diff --git a/.github/workflows/formatTag/action.yml b/.github/workflows/formatTag/action.yml deleted file mode 100644 index baef702..0000000 --- a/.github/workflows/formatTag/action.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: 'Format Tag' -description: 'Formats a git tag to remove potentially prefixes' -inputs: - tag: - description: 'The input tag' - required: true -outputs: - subpackage: - description: 'Whether this tag is a subpackage tag' - package: - description: 'The package string that was extracted from this tag' - semver: - description: 'The semver string that was extracted from this tag' -runs: - using: node20 - main: ./index.js diff --git a/.github/workflows/formatTag/formatTag.js b/.github/workflows/formatTag/formatTag.js deleted file mode 100644 index 0472645..0000000 --- a/.github/workflows/formatTag/formatTag.js +++ /dev/null @@ -1,22 +0,0 @@ -export function formatTag(tag) { - const parsed = /(?:^@.*\/(?.*)@v?)?(?\d+.\d+.\d+)-?.*/.exec( - tag - ); - const parsedPackage = /(?.*)@v?-?.*/.exec(tag); - - if (parsed?.groups) { - const isSubPackage = typeof parsed.groups.package === "string"; - const pkg = isSubPackage - ? parsed.groups.package - : parsedPackage?.groups?.package ?? "wapi.go"; - const semver = parsed.groups.semver; - - return { - isSubpackage: isSubPackage, - package: pkg, - semver, - }; - } - - return null; -} diff --git a/.github/workflows/formatTag/index.js b/.github/workflows/formatTag/index.js deleted file mode 100644 index 5ddc8d9..0000000 --- a/.github/workflows/formatTag/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import { getInput, setOutput } from "@actions/core"; -import { formatTag } from "./formatTag.js"; - -const tag = getInput("tag", { required: true }); -const parsed = formatTag(tag); - -if (parsed) { - setOutput("subpackage", parsed.isSubpackage); - setOutput("package", parsed.package); - setOutput("semver", parsed.semver); -} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..1780d23 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,36 @@ +name: Release + +on: + workflow_dispatch: + inputs: + branch: + description: "Branch" + default: "master" + commitHash: + description: "Commit Hash" + default: "HEAD" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.22.3" + + - name: User Node.js LTS + uses: actions/setup-node@v2 + with: + node-version: 20 + + - name: Release + run: npx semantic-release --ci + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..beac72b --- /dev/null +++ b/.releaserc @@ -0,0 +1,3 @@ +branches: + - master +repositoryUrl: https://github.com/sarthakjdev/wapi.go diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d834159 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sarthak@softlancer.co. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/.github/COMMIT_CONVENTION.md b/COMMIT_CONVENTION.md similarity index 100% rename from .github/COMMIT_CONVENTION.md rename to COMMIT_CONVENTION.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5b49a43 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,142 @@ + + +# Contributing to Wapi.go + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) +- [Your First Code Contribution](#your-first-code-contribution) +- [Improving The Documentation](#improving-the-documentation) +- [Styleguides](#styleguides) +- [Commit Messages](#commit-messages) +- [Join The Project Team](#join-the-project-team) + +## Code of Conduct + +This project and everyone participating in it is governed by the +[Wapi.go Code of Conduct](https://github.comsarthakjdev/wapi.go/blob//CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior +to <>. + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://golang.wapikit.com). + +Before you ask a question, it is best to search for existing [Issues](https://github.comsarthakjdev/wapi.go/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.comsarthakjdev/wapi.go/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + + + +## I Want To Contribute + +> ### Legal Notice +> +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence. + +### Reporting Bugs + + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://golang.wapikit.com). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.comsarthakjdev/wapi.go/issues?q=label%3Abug). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: +- Stack trace (Traceback) +- OS, Platform and Version (Windows, Linux, macOS, x86, ARM) +- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. +- Possibly your input and the output +- Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <>. + + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.comsarthakjdev/wapi.go/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the _reproduction steps_ that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). + + + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for Wapi.go, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation](https://golang.wapikit.com) carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.comsarthakjdev/wapi.go/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. + + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.comsarthakjdev/wapi.go/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. +- **Explain why this enhancement would be useful** to most Wapi.go users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + +#### Commit Messages + +Please follow the commit conventions mentioned in this document guide [here](./COMMIT_CONVENTION.md) diff --git a/Makefile b/Makefile index 90d113a..33fac9c 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,9 @@ $(GOMARKDOS): .PHONY: docs docs: $(GOMARKDOS) - mkdir -p docs/api-reference docs/guide && gomarkdoc -u --exclude-dirs ./examples/... --exclude-dirs ./internal/... -o './docs/api-reference/{{.Dir}}.mdx' --template-file file=./docs/templates/file.gotxt --template-file package=./docs/templates/package.gotxt ./... + mkdir -p docs/api-reference docs/guide && gomarkdoc -u --exclude-dirs ./examples/... --exclude-dirs ./internal/request_client/... --exclude-dirs ./cmd -o './docs/api-reference/{{.Dir}}.mdx' --template-file file=./docs/templates/file.gotxt --template-file package=./docs/templates/package.gotxt ./... .PHONY: format format: go fmt ./... + diff --git a/README.md b/README.md index 9cd72dc..7727939 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@

-@wapijs/wapi.js +@wapijs/Wapi.go


## 📌 Status -Beta Version - This library is not stable right now. It is currently in beta version. Report issues [here](https://github.com/sarthakjdev/wapi.go/issues). +Beta Version - This SDK is not stable right now. It is currently in beta version. Report issues [here](https://github.com/sarthakjdev/wapi.go/issues). ## 📖 About -Wapi.js is a JavaScript module, written in TypeScript, designed to interact with the WhatsApp cloud API in a user-friendly manner. +Wapi.go is a Golang SDK, that supports WhatsApp API products i.e., Business Management API and Cloud API +to build WhatsApp applications easily. ## ✨ Features @@ -33,7 +34,7 @@ This assumes you already have a working Go environment, if not please see go get github.com/sarthakjdev/wapi.go ``` -> Note: This library is not affiliated with the official WhatsApp Cloud API or does not act as any official solution provided the the Meta Inclusive Private Limited, this is just a open source library built for developers to support them in building whatsapp cloud api based chat bots easily. +> Note: This SDK is not affiliated with the official WhatsApp Cloud API or does not act as any official solution provided the the Meta Inclusive Private Limited, this is just a open source SDK built for developers to support them in building whatsapp cloud api based chat bots easily. ## 🚀 Usage @@ -46,6 +47,7 @@ This repository has three packages exported: - github.com/sarthakjdev/wapi.go/components - github.com/sarthakjdev/wapi.go/wapi/wapi +- github.com/sarthakjdev/wapi.go/wapi/business - github.com/sarthakjdev/wapi.go/wapi/events ```go @@ -71,11 +73,6 @@ whatsappClient, err := wapi.New(wapi.ClientConfig{ - **Notification Payloads**: Details can be found [here](https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components). - - ## 🤝 Contribution Guidelines 1. Fork the Project @@ -86,9 +83,16 @@ whatsappClient, err := wapi.New(wapi.ClientConfig{ For detailed guidelines, check [Contributing.md](./CONTRIBUTING.md). +# TODOs' + +- Handle errors gracefully +- Handle template and interactive messages gracefully +- Add support for more interactive messaged types like address input. +- + ## 📜 License -Distributed under the Apache 2.0 License. View [LICENSE](./LICENSE). +Distributed under the AGPL 3.0 License. View [LICENSE](./LICENSE). ## 📞 Contact @@ -96,4 +100,4 @@ Distributed under the Apache 2.0 License. View [LICENSE](./LICENSE). - Email: sarthak@softlancer.co - [Twitter](https://twitter.com/sarthakjdev) | [LinkedIn](https://www.linkedin.com/in/sarthakjdev) -Note: This library is part of an open-source product-building initiative by [Softlancer](https://github.com/softlancerhq), and this repository will soon be moved under the same organization. +Note: This SDK is part of an open-source product-building initiative by [Softlancer](https://github.com/softlancerhq), and this repository will soon be moved under the same organization. diff --git a/cmd/wapi/main.go b/cmd/wapi/main.go new file mode 100644 index 0000000..5346a8d --- /dev/null +++ b/cmd/wapi/main.go @@ -0,0 +1,12 @@ +package main + +// this cmd main package is to provide user with a cli to setup a bootstrapped wapi client + +func init() { + // init CLI flags + +} + +func main() { + +} diff --git a/docs/api-reference/cmd/wapi.mdx b/docs/api-reference/cmd/wapi.mdx new file mode 100644 index 0000000..19cd060 --- /dev/null +++ b/docs/api-reference/cmd/wapi.mdx @@ -0,0 +1,24 @@ +```go +import "github.com/sarthakjdev/wapi.go/cmd/wapi" +``` + + + + +## func init + +```go +func init() +``` + + + + +## func main + +```go +func main() +``` + + + diff --git a/docs/api-reference/internal.mdx b/docs/api-reference/internal.mdx index c6beab7..a377236 100644 --- a/docs/api-reference/internal.mdx +++ b/docs/api-reference/internal.mdx @@ -2,9 +2,6 @@ import "github.com/sarthakjdev/wapi.go/internal" ``` -## Index - -- [func GetValidator\(\) \*validator.Validate](<#GetValidator>) @@ -16,3 +13,19 @@ func GetValidator() *validator.Validate return a validator instance + +## type WhatsAppBusinessApiPaginationMeta + + + +```go +type WhatsAppBusinessApiPaginationMeta struct { + Paging struct { + Cursors struct { + Before string `json:"before,omitempty"` + After string `json:"after,omitempty"` + } `json:"cursors,omitempty"` + } `json:"paging,omitempty"` +} +``` + diff --git a/docs/api-reference/internal/manager.mdx b/docs/api-reference/internal/manager.mdx new file mode 100644 index 0000000..2a849d0 --- /dev/null +++ b/docs/api-reference/internal/manager.mdx @@ -0,0 +1,1550 @@ +```go +import "github.com/sarthakjdev/wapi.go/internal/manager" +``` + + + + +## type AdInteractionSourceMediaTypeEnum + + + +```go +type AdInteractionSourceMediaTypeEnum string +``` + + + +```go +const ( + AdInteractionSourceMediaTypeImage AdInteractionSourceMediaTypeEnum = "image" + AdInteractionSourceMediaTypeVideo AdInteractionSourceMediaTypeEnum = "video" +) +``` + + +## type AdInteractionSourceTypeEnum + + + +```go +type AdInteractionSourceTypeEnum string +``` + + + +```go +const ( + AdInteractionSourceTypeUnknown AdInteractionSourceTypeEnum = "unknown" +) +``` + + +## type Change + + + +```go +type Change struct { + Value Value `json:"value"` + Field string `json:"field"` +} +``` + + +## type ChannelEvent + +ChannelEvent represents an event that can be published and subscribed to. + +```go +type ChannelEvent struct { + Type events.EventType // Type is the type of the event. + Data events.BaseEvent // Data is the data associated with the event. +} +``` + + +## type Contact + + + +```go +type Contact struct { + WaId string `json:"wa_id"` + Profile Profile `json:"profile"` +} +``` + + +## type Conversation + + + +```go +type Conversation struct { + Id string `json:"id"` + Origin Origin `json:"origin,omitempty"` +} +``` + + +## type CreatePhoneNumberResponse + + + +```go +type CreatePhoneNumberResponse struct { + Id string `json:"id,omitempty"` +} +``` + + +## type DeleteQrCodeResponse + +DeleteQrCodeResponse represents the response of deleting a QR code. + +```go +type DeleteQrCodeResponse struct { + Success bool `json:"success,omitempty"` +} +``` + + +## type Entry + + + +```go +type Entry struct { + Id string `json:"id"` + Changes []Change `json:"changes"` +} +``` + + +## type Error + + + +```go +type Error struct { +} +``` + + +## type EventManager + +EventManager is responsible for managing events and their subscribers. + +```go +type EventManager struct { + subscribers map[events.EventType]chan ChannelEvent // subscribers is a map of event types to channels of ChannelEvent. + sync.RWMutex // RWMutex is used to synchronize access to the subscribers map. +} +``` + + +### func NewEventManager + +```go +func NewEventManager() *EventManager +``` + +NewEventManager creates a new instance of EventManger. + + +### func \(\*EventManager\) On + +```go +func (em *EventManager) On(eventName events.EventType, handler func(events.BaseEvent)) events.EventType +``` + +On registers a handler function for the specified event type. The handler function will be called whenever the event is published. It returns the event type that the handler is registered for. + + +### func \(\*EventManager\) Publish + +```go +func (em *EventManager) Publish(event events.EventType, data events.BaseEvent) error +``` + +Publish publishes an event to the event system and notifies all the subscribers. + + +### func \(\*EventManager\) Subscribe + +```go +func (em *EventManager) Subscribe(eventName events.EventType) (chan ChannelEvent, error) +``` + +Subscribe adds a new subscriber to the specified event type. The subscriber will be notified when the event is published. + + +### func \(\*EventManager\) Unsubscribe + +```go +func (em *EventManager) Unsubscribe(id events.EventType) +``` + +Unsubscribe removes a subscriber from the specified event type. + + +## type FetchPhoneNumberFilters + +FetchPhoneNumberFilters holds the filters for fetching phone numbers. + +```go +type FetchPhoneNumberFilters struct { + GetSandboxNumbers bool +} +``` + + +## type GenerateQrCodeResponse + +GenerateQrCodeResponse represents the response of generating a QR code. + +```go +type GenerateQrCodeResponse struct { + Code string `json:"code,omitempty"` + PrefilledMessage string `json:"prefilled_message,omitempty"` + DeepLinkUrl string `json:"deep_link_url,omitempty"` + QrImageUrl string `json:"qr_image_url,omitempty"` +} +``` + + +## type GetAllQrCodesResponse + +GetAllQrCodesResponse represents the response of getting all QR codes for a phone number. + +```go +type GetAllQrCodesResponse struct { + Data []GenerateQrCodeResponse `json:"data,omitempty"` +} +``` + + +## type InteractiveNotificationTypeEnum + + + +```go +type InteractiveNotificationTypeEnum string +``` + + + +```go +const ( + NotificationTypeButtonReply InteractiveNotificationTypeEnum = "button_reply" + NotificationTypeListReply InteractiveNotificationTypeEnum = "list_reply" +) +``` + + +## type MediaManager + +MediaManager is responsible for managing media related operations. + +```go +type MediaManager struct { + requester request_client.RequestClient +} +``` + + +### func NewMediaManager + +```go +func NewMediaManager(requester request_client.RequestClient) *MediaManager +``` + +NewMediaManager creates a new instance of MediaManager. + + +### func \(\*MediaManager\) GetMediaIdByUrl + +```go +func (mm *MediaManager) GetMediaIdByUrl(id string) +``` + +GetMediaIdByUrl retrieves the media ID by its URL. + + +### func \(\*MediaManager\) GetMediaUrlById + +```go +func (mm *MediaManager) GetMediaUrlById(id string) +``` + +GetMediaUrlById retrieves the media URL by its ID. + + +## type Message + + + +```go +type Message struct { + Id string `json:"id"` + From string `json:"from"` + Timestamp string `json:"timestamp"` + Type NotificationMessageTypeEnum `json:"type"` + Context NotificationPayloadMessageContextSchemaType `json:"context"` + Errors []Error `json:",inline"` + NotificationPayloadTextMessageSchemaType `json:",inline"` + NotificationPayloadAudioMessageSchemaType `json:",inline"` + NotificationPayloadImageMessageSchemaType `json:",inline"` + NotificationPayloadButtonMessageSchemaType `json:",inline"` + NotificationPayloadDocumentMessageSchemaType `json:",inline"` + NotificationPayloadOrderMessageSchemaType `json:",inline"` + NotificationPayloadStickerMessageSchemaType `json:",inline"` + NotificationPayloadSystemMessageSchemaType `json:",inline"` + NotificationPayloadVideoMessageSchemaType `json:",inline"` + NotificationPayloadReactionMessageSchemaType `json:",inline"` + NotificationPayloadLocationMessageSchemaType `json:",inline"` + NotificationPayloadContactMessageSchemaType `json:",inline"` + NotificationPayloadInteractionMessageSchemaType `json:",inline"` +} +``` + + +## type MessageManager + +MessageManager is responsible for managing messages. + +```go +type MessageManager struct { + requester request_client.RequestClient + PhoneNumberId string +} +``` + + +### func NewMessageManager + +```go +func NewMessageManager(requester request_client.RequestClient, phoneNumberId string) *MessageManager +``` + +NewMessageManager creates a new instance of MessageManager. + + +### func \(\*MessageManager\) Send + +```go +func (mm *MessageManager) Send(message components.BaseMessage, phoneNumber string) (string, error) +``` + +Send sends a message with the given parameters and returns the response. TODO: return the structured response from here + + +## type MessageStatusCategoryEnum + + + +```go +type MessageStatusCategoryEnum string +``` + + + +```go +const ( + MessageStatusCategorySent MessageStatusCategoryEnum = "sent" +) +``` + + +## type MessageStatusEnum + + + +```go +type MessageStatusEnum string +``` + + + +```go +const ( + MessageStatusDelivered MessageStatusEnum = "delivered" + MessageStatusRead MessageStatusEnum = "read" + MessageStatusUnDelivered MessageStatusEnum = "undelivered" + MessageStatusFailed MessageStatusEnum = "failed" + MessageStatusSent MessageStatusEnum = "sent" +) +``` + + +## type MessageTemplateCategory + +MessageTemplateCategory represents the category of a WhatsApp Business message template. + +```go +type MessageTemplateCategory string +``` + +Constants representing different message template categories. + +```go +const ( + MessageTemplateCategoryUtility MessageTemplateCategory = "UTILITY" + MessageTemplateCategoryMarketing MessageTemplateCategory = "MARKETING" + MessageTemplateCategoryAuthentication MessageTemplateCategory = "AUTHENTICATION" +) +``` + + +## type MessageTemplateComponentFormat + +MessageTemplateComponentFormat represents the format of a message template component. + +```go +type MessageTemplateComponentFormat string +``` + +Constants representing different message template component formats. + +```go +const ( + MessageTemplateComponentFormatText MessageTemplateComponentFormat = "TEXT" + MessageTemplateComponentFormatImage MessageTemplateComponentFormat = "IMAGE" + MessageTemplateComponentFormatDocument MessageTemplateComponentFormat = "DOCUMENT" + MessageTemplateComponentFormatVideo MessageTemplateComponentFormat = "VIDEO" + MessageTemplateComponentFormatLocation MessageTemplateComponentFormat = "LOCATION" +) +``` + + +## type MessageTemplateComponentType + +MessageTemplateComponentType represents the type of a message template component. + +```go +type MessageTemplateComponentType string +``` + +Constants representing different message template component types. + +```go +const ( + MessageTemplateComponentTypeGreeting MessageTemplateComponentType = "GREETING" + MessageTemplateComponentTypeHeader MessageTemplateComponentType = "HEADER" + MessageTemplateComponentTypeBody MessageTemplateComponentType = "BODY" + MessageTemplateComponentTypeFooter MessageTemplateComponentType = "FOOTER" + MessageTemplateComponentTypeButtons MessageTemplateComponentType = "BUTTONS" + MessageTemplateComponentTypeCarousel MessageTemplateComponentType = "CAROUSEL" + MessageTemplateComponentTypeLimitedTimeOffer MessageTemplateComponentType = "LIMITED_TIME_OFFER" +) +``` + + +## type MessageTemplateCreationResponse + + + +```go +type MessageTemplateCreationResponse struct { + Id string `json:"id,omitempty"` + Status MessageTemplateStatus `json:"status,omitempty"` + Category MessageTemplateCategory `json:"category,omitempty"` +} +``` + + +## type MessageTemplateStatus + +MessageTemplateStatus represents the status of a WhatsApp Business message template. + +```go +type MessageTemplateStatus string +``` + +Constants representing different message template statuses. + +```go +const ( + MessageTemplateStatusApproved MessageTemplateStatus = "APPROVED" + MessageTemplateStatusRejected MessageTemplateStatus = "REJECTED" + MessageTemplateStatusPending MessageTemplateStatus = "PENDING" +) +``` + + +## type Metadata + + + +```go +type Metadata struct { + DisplayPhoneNumber string `json:"display_phone_number"` + PhoneNumberId string `json:"phone_number_id"` +} +``` + + +## type NotificationMessageTypeEnum + + + +```go +type NotificationMessageTypeEnum string +``` + + + +```go +const ( + NotificationMessageTypeText NotificationMessageTypeEnum = "text" + NotificationMessageTypeAudio NotificationMessageTypeEnum = "audio" + NotificationMessageTypeImage NotificationMessageTypeEnum = "image" + NotificationMessageTypeButton NotificationMessageTypeEnum = "button" + NotificationMessageTypeDocument NotificationMessageTypeEnum = "document" + NotificationMessageTypeOrder NotificationMessageTypeEnum = "order" + NotificationMessageTypeSticker NotificationMessageTypeEnum = "sticker" + NotificationMessageTypeSystem NotificationMessageTypeEnum = "system" + NotificationMessageTypeVideo NotificationMessageTypeEnum = "video" + NotificationMessageTypeReaction NotificationMessageTypeEnum = "reaction" + NotificationMessageTypeInteractive NotificationMessageTypeEnum = "interactive" + NotificationMessageTypeUnknown NotificationMessageTypeEnum = "unknown" + NotificationMessageTypeLocation NotificationMessageTypeEnum = "location" + NotificationMessageTypeContacts NotificationMessageTypeEnum = "contacts" +) +``` + + +## type NotificationPayloadAudioMessageSchemaType + + + +```go +type NotificationPayloadAudioMessageSchemaType struct { + Audio struct { + Id string `json:"id,omitempty"` + MIMEType string `json:"mime_type,omitempty"` + SHA256 string `json:"sha256,omitempty"` + } `json:"audio,omitempty"` +} +``` + + +## type NotificationPayloadButtonInteractionMessageSchemaType + + + +```go +type NotificationPayloadButtonInteractionMessageSchemaType struct { + ButtonReply struct { + ReplyId string `json:"reply_id"` + Title string `json:"title"` + } `json:"button_reply,omitempty"` +} +``` + + +## type NotificationPayloadButtonMessageSchemaType + + + +```go +type NotificationPayloadButtonMessageSchemaType struct { + Button struct { + Payload string `json:"payload"` + Text string `json:"text"` + } `json:"button,omitempty"` +} +``` + + +## type NotificationPayloadContactMessageSchemaType + + + +```go +type NotificationPayloadContactMessageSchemaType struct { + Contacts []Contact `json:"contacts"` +} +``` + + +## type NotificationPayloadDocumentMessageSchemaType + + + +```go +type NotificationPayloadDocumentMessageSchemaType struct { + Document struct { + Id string `json:"id"` + MIMEType string `json:"mime_type"` + SHA256 string `json:"sha256"` + Caption string `json:"caption,omitempty"` + Filename string `json:"filename,omitempty"` + } `json:"document,omitempty"` +} +``` + + +## type NotificationPayloadErrorSchemaType + + + +```go +type NotificationPayloadErrorSchemaType struct { + Code int `json:"code"` + Title string `json:"title"` + Message string `json:"message"` + ErrorData struct { + Details string `json:"details"` + } `json:"error_data,omitempty"` +} +``` + + +## type NotificationPayloadImageMessageSchemaType + + + +```go +type NotificationPayloadImageMessageSchemaType struct { + Image struct { + Id string `json:"id"` + MIMEType string `json:"mime_type"` + SHA256 string `json:"sha256"` + Caption string `json:"caption,omitempty"` + } `json:"image,omitempty"` +} +``` + + +## type NotificationPayloadInteractionMessageSchemaType + + + +```go +type NotificationPayloadInteractionMessageSchemaType struct { + Interactive struct { + Type InteractiveNotificationTypeEnum `json:"type"` + NotificationPayloadButtonInteractionMessageSchemaType `json:",inline,omitempty"` + NotificationPayloadListInteractionMessageSchemaType `json:",inline,omitempty"` + } `json:"interactive,omitempty"` +} +``` + + +## type NotificationPayloadListInteractionMessageSchemaType + + + +```go +type NotificationPayloadListInteractionMessageSchemaType struct { + ListReply struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + } `json:"list_reply,omitempty"` +} +``` + + +## type NotificationPayloadLocationMessageSchemaType + + + +```go +type NotificationPayloadLocationMessageSchemaType struct { + Location struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Name string `json:"name,omitempty"` + Address string `json:"address,omitempty"` + } `json:"location,omitempty"` +} +``` + + +## type NotificationPayloadMessageContextSchemaType + + + +```go +type NotificationPayloadMessageContextSchemaType struct { + Forwarded bool `json:"forwarded,omitempty"` + FrequentlyForwarded bool `json:"frequently_forwarded,omitempty"` + From string `json:"from,omitempty"` + Id string `json:"id"` + ReferredProduct struct { + CatalogId string `json:"catalog_id"` + ProductRetailerId string `json:"product_retailer_id"` + } `json:"referred_product,omitempty"` +} +``` + + +## type NotificationPayloadOrderMessageSchemaType + + + +```go +type NotificationPayloadOrderMessageSchemaType struct { + // OrderText string `json:"text"` + Order struct { + CatalogId string `json:"catalog_id"` + ProductItems []struct { + ProductRetailerId string `json:"product_retailer_id"` + Quantity string `json:"quantity"` + ItemPrice string `json:"item_price"` + Currency string `json:"currency"` + } `json:"product_items"` + } `json:"order,omitempty"` +} +``` + + +## type NotificationPayloadReactionMessageSchemaType + + + +```go +type NotificationPayloadReactionMessageSchemaType struct { + Reaction struct { + MessageId string `json:"message_id"` + Emoji string `json:"emoji"` + } `json:"reaction,omitempty"` +} +``` + + +## type NotificationPayloadStickerMessageSchemaType + + + +```go +type NotificationPayloadStickerMessageSchemaType struct { + Sticker struct { + Id string `json:"id"` + MIMEType string `json:"mime_type"` + SHA256 string `json:"sha256"` + Animated bool `json:"animated"` + } `json:"sticker,omitempty"` +} +``` + + +## type NotificationPayloadSystemMessageSchemaType + + + +```go +type NotificationPayloadSystemMessageSchemaType struct { + System struct { + Identity string `json:"identity"` + Body string `json:"body"` + Customer string `json:"customer"` + Type SystemNotificationTypeEnum `json:"type"` + WaId string `json:"wa_id"` + } `json:"system,omitempty"` + Identity struct { + Acknowledged string `json:"acknowledged"` + CreatedTimestamp string `json:"created_timestamp"` + Hash string `json:"hash"` + } `json:"identity,omitempty"` +} +``` + + +## type NotificationPayloadTextMessageSchemaType + + + +```go +type NotificationPayloadTextMessageSchemaType struct { + Text struct { + Body string `json:"body"` + } `json:"text,omitempty"` + Referral struct { + SourceUrl string `json:"source_url"` + SourceType AdInteractionSourceTypeEnum `json:"source_type"` + SourceId string `json:"source_id"` + Headline string `json:"headline"` + Body string `json:"body"` + ImageUrl string `json:"image_url,omitempty"` + VideoUrl string `json:"video_url,omitempty"` + ThumbnailUrl string `json:"thumbnail_url"` + CtwaCLId string `json:"ctwa_clid"` + MediaType AdInteractionSourceMediaTypeEnum `json:"media_type"` + } `json:"referral,omitempty"` +} +``` + + +## type NotificationPayloadVideoMessageSchemaType + + + +```go +type NotificationPayloadVideoMessageSchemaType struct { + Video struct { + Id string `json:"id"` + MIMEType string `json:"mime_type"` + SHA256 string `json:"sha256"` + Caption string `json:"caption,omitempty"` + Filename string `json:"filename,omitempty"` + } `json:"video,omitempty"` +} +``` + + +## type NotificationReasonEnum + + + +```go +type NotificationReasonEnum string +``` + + + +```go +const ( + NotificationReasonMessage NotificationReasonEnum = "message" +) +``` + + +## type Origin + + + +```go +type Origin struct { + Type MessageStatusCategoryEnum `json:"type"` + ExpirationTimestamp string `json:"expiration_timestamp,omitempty"` +} +``` + + +## type PhoneNumberManager + +PhoneNumberManager is responsible for managing phone numbers for WhatsApp Business API and phone number specific operations. + +```go +type PhoneNumberManager struct { + businessAccountId string + apiAccessToken string + requester *request_client.RequestClient +} +``` + + +### func NewPhoneNumberManager + +```go +func NewPhoneNumberManager(config *PhoneNumberManagerConfig) *PhoneNumberManager +``` + +NewPhoneNumberManager creates a new instance of PhoneNumberManager. + + +### func \(\*PhoneNumberManager\) Create + +```go +func (manager *PhoneNumberManager) Create(phoneNumber, verifiedName, countryCode string) (CreatePhoneNumberResponse, error) +``` + + + + +### func \(\*PhoneNumberManager\) DeleteQrCode + +```go +func (manager *PhoneNumberManager) DeleteQrCode(phoneNumber, id string) (*DeleteQrCodeResponse, error) +``` + +DeleteQrCode deletes a QR code by its ID for the specified phone number. + + +### func \(\*PhoneNumberManager\) Fetch + +```go +func (manager *PhoneNumberManager) Fetch(phoneNumberId string) (*WhatsappBusinessAccountPhoneNumber, error) +``` + +Fetch fetches a phone number by its ID. + + +### func \(\*PhoneNumberManager\) FetchAll + +```go +func (manager *PhoneNumberManager) FetchAll(options FetchPhoneNumberFilters) (*WhatsappBusinessAccountPhoneNumberEdge, error) +``` + +FetchAll fetches all phone numbers based on the provided filters. + + +### func \(\*PhoneNumberManager\) GenerateQrCode + +```go +func (manager *PhoneNumberManager) GenerateQrCode(phoneNumber string, prefilledMessage string) (*GenerateQrCodeResponse, error) +``` + +GenerateQrCode generates a QR code for the specified phone number with the given prefilled message. + + +### func \(\*PhoneNumberManager\) GetAllQrCodes + +```go +func (manager *PhoneNumberManager) GetAllQrCodes(phoneNumber string) (*GetAllQrCodesResponse, error) +``` + +GetAllQrCodes gets all QR codes for the specified phone number. + + +### func \(\*PhoneNumberManager\) GetQrCodeById + +```go +func (manager *PhoneNumberManager) GetQrCodeById(phoneNumber, id string) (*GetAllQrCodesResponse, error) +``` + +GetQrCodeById gets a QR code by its ID for the specified phone number. + + +### func \(\*PhoneNumberManager\) RequestVerificationCode + +```go +func (manager *PhoneNumberManager) RequestVerificationCode(phoneNumberId string, codeMethod VerifyCodeMethod, languageCode string) (RequestVerificationCodeResponse, error) +``` + + + + +### func \(\*PhoneNumberManager\) UpdateQrCode + +```go +func (manager *PhoneNumberManager) UpdateQrCode(phoneNumber, id, prefilledMessage string) (*GenerateQrCodeResponse, error) +``` + +UpdateQrCode updates a QR code by its ID for the specified phone number with the given prefilled message. + + +### func \(\*PhoneNumberManager\) VerifyCode + +```go +func (manager *PhoneNumberManager) VerifyCode(phoneNumberId, verificationCode string) (VerifyCodeResponse, error) +``` + + + + +## type PhoneNumberManagerConfig + +PhoneNumberManagerConfig holds the configuration for PhoneNumberManager. + +```go +type PhoneNumberManagerConfig struct { + BusinessAccountId string + ApiAccessToken string + Requester *request_client.RequestClient +} +``` + + +## type Pricing + + + +```go +type Pricing struct { + PricingModel string `json:"pricing_model"` + Category MessageStatusCategoryEnum `json:"category"` +} +``` + + +## type Profile + + + +```go +type Profile struct { + Name string `json:"name"` +} +``` + + +## type RequestVerificationCodeResponse + + + +```go +type RequestVerificationCodeResponse struct { + Success bool `json:"success,omitempty"` +} +``` + + +## type Status + + + +```go +type Status struct { + Conversation Conversation `json:"conversation,omitempty"` + Errors []Error `json:"errors,omitempty"` + Status string `json:"status"` + Timestamp string `json:"timestamp"` + RecipientId string `json:"recipient_id"` + Pricing Pricing `json:"pricing,omitempty"` +} +``` + + +## type SystemNotificationTypeEnum + + + +```go +type SystemNotificationTypeEnum string +``` + + + +```go +const ( + SystemNotificationTypeCustomerPhoneNumberChange SystemNotificationTypeEnum = "user_changed_number" + SystemNotificationTypeCustomerIdentityChanged SystemNotificationTypeEnum = "customer_identity_changed" +) +``` + + +## type TemplateAnalyticsType + + + +```go +type TemplateAnalyticsType struct { +} +``` + + +## type TemplateManager + +TemplateManager is responsible for managing WhatsApp Business message templates. + +```go +type TemplateManager struct { + businessAccountId string + apiAccessToken string + requester *request_client.RequestClient +} +``` + + +### func NewTemplateManager + +```go +func NewTemplateManager(config *TemplateManagerConfig) *TemplateManager +``` + +NewTemplateManager creates a new TemplateManager with the given configuration. + + +### func \(\*TemplateManager\) Create + +```go +func (manager *TemplateManager) Create(body WhatsappMessageTemplateCreateRequestBody) (*MessageTemplateCreationResponse, error) +``` + + + + +### func \(\*TemplateManager\) Delete + +```go +func (tm *TemplateManager) Delete(id string) +``` + + + + +### func \(\*TemplateManager\) Fetch + +```go +func (manager *TemplateManager) Fetch(Id string) (*WhatsAppBusinessMessageTemplateNode, error) +``` + +Fetch fetches a single WhatsApp Business message template by its ID. + + +### func \(\*TemplateManager\) FetchAll + +```go +func (manager *TemplateManager) FetchAll() (*WhatsAppBusinessTemplatesFetchResponseEdge, error) +``` + +FetchAll fetches all WhatsApp Business message templates. + + +### func \(\*TemplateManager\) FetchMessageTemplatePreviews + +```go +func (tm *TemplateManager) FetchMessageTemplatePreviews() +``` + + + + +### func \(\*TemplateManager\) FetchPerformanceAnalytics + +```go +func (manager *TemplateManager) FetchPerformanceAnalytics(templateName, templateId string) (string, error) +``` + + + + +### func \(\*TemplateManager\) MigrateFromOtherBusinessAccount + +```go +func (manager *TemplateManager) MigrateFromOtherBusinessAccount(sourcePageNumber int, sourceWabaId int) (string, error) +``` + + + + +### func \(\*TemplateManager\) Update + +```go +func (manager *TemplateManager) Update(templateId string, updates WhatsAppBusinessAccountMessageTemplateUpdateRequestBody) (*MessageTemplateCreationResponse, error) +``` + + + + +## type TemplateManagerConfig + +TemplateManagerConfig represents the configuration for creating a new TemplateManager. + +```go +type TemplateManagerConfig struct { + BusinessAccountId string + ApiAccessToken string + Requester *request_client.RequestClient +} +``` + + +## type TemplateMessagePreviewEdge + + + +```go +type TemplateMessagePreviewEdge struct { + Data []TemplateMessagePreviewNode `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` +} +``` + + +## type TemplateMessagePreviewNode + + + +```go +type TemplateMessagePreviewNode struct { + Body string `json:"body,omitempty"` + Buttons []WhatsAppBusinessAccountMessageTemplatePreviewButton `json:"buttons,omitempty"` + Footer string `json:"footer,omitempty"` + Header string `json:"header,omitempty"` + Language string `json:"language,omitempty"` +} +``` + + +## type TemplatePerformanceAnalytics + + + +```go +type TemplatePerformanceAnalytics struct { +} +``` + + +## type Value + + + +```go +type Value struct { + MessagingProduct string `json:"messaging_product"` + Metadata Metadata `json:"metadata"` + Contacts []Contact `json:"contacts,omitempty"` + Statuses []Status `json:"statuses,omitempty"` + Messages []Message `json:"messages,omitempty"` + Errors []Error `json:"errors,omitempty"` +} +``` + + +## type VerifyCodeMethod + + + +```go +type VerifyCodeMethod string +``` + + + +```go +const ( + VerifyCodeMethodSms VerifyCodeMethod = "SMS" + VerifyCodeMethodVoice VerifyCodeMethod = "VOICE" +) +``` + + +## type VerifyCodeResponse + + + +```go +type VerifyCodeResponse struct { + Success bool `json:"success,omitempty"` +} +``` + + +## type WebhookManager + +WebhookManager represents a manager for handling webhooks. + +```go +type WebhookManager struct { + secret string + path string + port int + EventManager EventManager + Requester request_client.RequestClient +} +``` + + +### func NewWebhook + +```go +func NewWebhook(options *WebhookManagerConfig) *WebhookManager +``` + +NewWebhook creates a new WebhookManager with the given options. + + +### func \(\*WebhookManager\) GetRequestHandler + +```go +func (wh *WebhookManager) GetRequestHandler(c echo.Context) error +``` + +GetRequestHandler handles GET requests to the webhook endpoint. + + +### func \(\*WebhookManager\) ListenToEvents + +```go +func (wh *WebhookManager) ListenToEvents() +``` + +ListenToEvents starts listening to events and handles incoming requests. + + +### func \(\*WebhookManager\) PostRequestHandler + +```go +func (wh *WebhookManager) PostRequestHandler(c echo.Context) error +``` + +PostRequestHandler handles POST requests to the webhook endpoint. + + +### func \(\*WebhookManager\) createEchoHttpServer + +```go +func (wh *WebhookManager) createEchoHttpServer() *echo.Echo +``` + +createEchoHttpServer creates a new instance of Echo HTTP server. This function is used in case the client has not provided any custom HTTP server. + + +## type WebhookManagerConfig + +WebhookManagerConfig represents the configuration options for creating a new WebhookManager. + +```go +type WebhookManagerConfig struct { + Secret string `validate:"required"` + EventManager EventManager `validate:"required"` + Requester request_client.RequestClient `validate:"required"` + Path string + Port int +} +``` + + +## type WhatsAppBusinessAccountMessageTemplateDeleteRequestBody + + + +```go +type WhatsAppBusinessAccountMessageTemplateDeleteRequestBody struct { + HsmId string `json:"hsm_id,omitempty"` + Name string `json:"name,omitempty"` +} +``` + + +## type WhatsAppBusinessAccountMessageTemplatePreviewButton + + + +```go +type WhatsAppBusinessAccountMessageTemplatePreviewButton struct { + AutoFillText string `json:"auto_fill_text,omitempty"` + Text string `json:"text,omitempty"` +} +``` + + +## type WhatsAppBusinessAccountMessageTemplateUpdateRequestBody + +this is the request body for the message template update request + +```go +type WhatsAppBusinessAccountMessageTemplateUpdateRequestBody struct { + Components []WhatsappMessageTemplateComponentCreateOrUpdateRequestBody `json:"components,omitempty"` + Category string `json:"category,omitempty"` + MessageSendTtlSeconds int `json:"message_send_ttl_seconds,omitempty"` +} +``` + + +## type WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape + +WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape represents the quality score of a WhatsApp Business message template. + +```go +type WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape struct { + Date int `json:"date,omitempty"` + Reasons []string `json:"reasons,omitempty"` + Score int `json:"score,omitempty"` +} +``` + + +## type WhatsAppBusinessHSMWhatsAppHSMComponent + +WhatsAppBusinessHSMWhatsAppHSMComponent represents a component in a WhatsApp Business message template. + +```go +type WhatsAppBusinessHSMWhatsAppHSMComponent struct { + AddSecurityRecommendation bool `json:"add_security_recommendation,omitempty"` + Buttons []WhatsAppBusinessHSMWhatsAppHSMComponentButton `json:"buttons,omitempty"` + Cards []WhatsAppBusinessHSMWhatsAppHSMComponentCard `json:"cards,omitempty"` + CodeExpirationMinutes int `json:"code_expiration_minutes,omitempty"` + Example WhatsAppBusinessHSMWhatsAppHSMComponentExample `json:"example,omitempty"` + Format string `json:"format,omitempty"` + LimitedTimeOffer WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape `json:"limited_time_offer,omitempty"` + Text string `json:"text,omitempty"` + Type string `json:"type,omitempty"` +} +``` + + +## type WhatsAppBusinessHSMWhatsAppHSMComponentButton + +WhatsAppBusinessHSMWhatsAppHSMComponentButton represents a button component in a WhatsApp Business message template. + +```go +type WhatsAppBusinessHSMWhatsAppHSMComponentButton struct { +} +``` + + +## type WhatsAppBusinessHSMWhatsAppHSMComponentCard + +WhatsAppBusinessHSMWhatsAppHSMComponentCard represents a card component in a WhatsApp Business message template. + +```go +type WhatsAppBusinessHSMWhatsAppHSMComponentCard struct { +} +``` + + +## type WhatsAppBusinessHSMWhatsAppHSMComponentExample + +WhatsAppBusinessHSMWhatsAppHSMComponentExample represents an example component in a WhatsApp Business message template. + +```go +type WhatsAppBusinessHSMWhatsAppHSMComponentExample struct { +} +``` + + +## type WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape + +WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape represents a limited time offer parameter in a WhatsApp Business message template. + +```go +type WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape struct { +} +``` + + +## type WhatsAppBusinessMessageTemplateNode + +WhatsAppBusinessMessageTemplateNode represents a WhatsApp Business message template. + +```go +type WhatsAppBusinessMessageTemplateNode struct { + Id string `json:"id,omitempty"` + Category MessageTemplateCategory `json:"category,omitempty"` + Components []WhatsAppBusinessHSMWhatsAppHSMComponent `json:"components,omitempty"` + CorrectCategory string `json:"correct_category,omitempty"` + CtaUrlLinkTrackingOptedOut bool `json:"cta_url_link_tracking_opted_out,omitempty"` + Language string `json:"language,omitempty"` + LibraryTemplateName string `json:"library_template_name,omitempty"` + MessageSendTtlSeconds int `json:"message_send_ttl_seconds,omitempty"` + Name string `json:"name,omitempty"` + PreviousCategory string `json:"previous_category,omitempty"` + QualityScore WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape `json:"quality_score,omitempty"` + RejectedReason string `json:"rejected_reason,omitempty"` + Status MessageTemplateStatus `json:"status,omitempty"` +} +``` + + +## type WhatsAppBusinessTemplatesFetchResponseEdge + +WhatsAppBusinessTemplatesFetchResponseEdge represents the response structure for fetching WhatsApp Business message templates. + +```go +type WhatsAppBusinessTemplatesFetchResponseEdge struct { + Data []WhatsAppBusinessMessageTemplateNode `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` +} +``` + + +## type WhatsappApiNotificationPayloadSchemaType + + + +```go +type WhatsappApiNotificationPayloadSchemaType struct { + Object string `json:"object"` + Entry []Entry `json:"entry"` +} +``` + + +## type WhatsappBusinessAccountPhoneNumber + +WhatsappBusinessAccountPhoneNumber represents a WhatsApp Business Account phone number. + +```go +type WhatsappBusinessAccountPhoneNumber struct { + VerifiedName string `json:"verified_name,omitempty"` + DisplayPhoneNumber string `json:"display_phone_number,omitempty"` + Id string `json:"id,omitempty"` + QualityRating string `json:"quality_rating,omitempty"` + CodeVerification struct { + Status string `json:"code_verification_status,omitempty"` + } `json:"code_verification_status,omitempty"` + PlatformType string `json:"platform_type,omitempty"` +} +``` + + +## type WhatsappBusinessAccountPhoneNumberEdge + +WhatsappBusinessAccountPhoneNumberEdge represents a list of WhatsApp Business Account phone numbers. + +```go +type WhatsappBusinessAccountPhoneNumberEdge struct { + Data []WhatsappBusinessAccountPhoneNumber `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` + Summary string `json:"summary,omitempty"` +} +``` + + +## type WhatsappMessageTemplateButtonCreateRequestBody + +WhatsappMessageTemplateButtonCreateRequestBody represents the request body for creating a button in a message template. + +```go +type WhatsappMessageTemplateButtonCreateRequestBody struct { + // enum {QUICK_REPLY, URL, PHONE_NUMBER, OTP, MPM, CATALOG, FLOW, VOICE_CALL} + Type string `json:"type,omitempty"` + Text string `json:"text,omitempty"` + Url string `json:"url,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + Example string `json:"example,omitempty"` + FlowId string `json:"flow_id,omitempty"` + ZeroTapTermsAccepted bool `json:"zero_tap_terms_accepted,omitempty"` +} +``` + + +## type WhatsappMessageTemplateComponentCreateOrUpdateRequestBody + +WhatsappMessageTemplateComponentCreateOrUpdateRequestBody represents the request body for creating or updating a component in a message template. + +```go +type WhatsappMessageTemplateComponentCreateOrUpdateRequestBody struct { + Type MessageTemplateComponentType `json:"type,omitempty"` + Format MessageTemplateComponentFormat `json:"format,omitempty"` + Text string `json:"text,omitempty"` + Buttons []WhatsappMessageTemplateButtonCreateRequestBody `json:"buttons,omitempty"` +} +``` + + +### func \(\*WhatsappMessageTemplateComponentCreateOrUpdateRequestBody\) AddButton + +```go +func (component *WhatsappMessageTemplateComponentCreateOrUpdateRequestBody) AddButton(button WhatsappMessageTemplateButtonCreateRequestBody) +``` + +AddButton adds a button to the component. + + +## type WhatsappMessageTemplateCreateRequestBody + +WhatsappMessageTemplateCreateRequestBody represents the request body for creating a message template. + +```go +type WhatsappMessageTemplateCreateRequestBody struct { + AllowCategoryChange bool `json:"allow_category_change,omitempty" ` + + // enum {UTILITY, MARKETING, AUTHENTICATION} + Category string `json:"category,omitempty" validate:"required"` + + Components []WhatsappMessageTemplateComponentCreateOrUpdateRequestBody `json:"components" validate:"required"` + Name string `json:"name,omitempty" validate:"required"` + Language string `json:"language" validate:"required"` + LibraryTemplateName string `json:"library_template_name,omitempty"` + LibraryTemplateButtonInputs []WhatsappMessageTemplateButtonCreateRequestBody `json:"library_template_button_inputs,omitempty"` +} +``` + + +### func \(\*WhatsappMessageTemplateCreateRequestBody\) AddComponent + +```go +func (body *WhatsappMessageTemplateCreateRequestBody) AddComponent(component WhatsappMessageTemplateComponentCreateOrUpdateRequestBody) +``` + + + diff --git a/docs/api-reference/pkg/business.mdx b/docs/api-reference/pkg/business.mdx new file mode 100644 index 0000000..7024da6 --- /dev/null +++ b/docs/api-reference/pkg/business.mdx @@ -0,0 +1,436 @@ +```go +import "github.com/sarthakjdev/wapi.go/pkg/business" +``` + + + + +## type AccountAnalyticsOptions + + + +```go +type AccountAnalyticsOptions struct { + Start time.Time `json:"start" validate:"required"` + End time.Time `json:"end" validate:"required"` + Granularity AnalyticsRequestGranularityType `json:"granularity" validate:"required"` + PhoneNumbers []string `json:"phone_numbers,omitempty"` + + // * NOT SUPPORTED AS OF NOW + // ProductTypes []WhatsAppBusinessAccountAnalyticsProductType `json:"product_types,omitempty"` + CountryCodes []string `json:"country_codes,omitempty"` +} +``` + + +## type AnalyticsDataPoint + + + +```go +type AnalyticsDataPoint struct { + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + Sent int `json:"sent,omitempty"` + Delivered int `json:"delivered,omitempty"` +} +``` + + +## type AnalyticsRequestGranularityType + + + +```go +type AnalyticsRequestGranularityType string +``` + + + +```go +const ( + AnalyticsRequestGranularityTypeHalfHour AnalyticsRequestGranularityType = "HALF_HOUR" + AnalyticsRequestGranularityTypeDay AnalyticsRequestGranularityType = "DAY" + AnalyticsRequestGranularityTypeMonth AnalyticsRequestGranularityType = "MONTH" +) +``` + + +## type BusinessClient + + + +```go +type BusinessClient struct { + BusinessAccountId string `json:"businessAccountId" validate:"required"` + AccessToken string `json:"accessToken" validate:"required"` + PhoneNumber *manager.PhoneNumberManager + Template *manager.TemplateManager + requester *request_client.RequestClient +} +``` + + +### func NewBusinessClient + +```go +func NewBusinessClient(config *BusinessClientConfig) *BusinessClient +``` + + + + +### func \(\*BusinessClient\) ConversationAnalytics + +```go +func (client *BusinessClient) ConversationAnalytics(options ConversationAnalyticsOptions) (*WhatsAppConversationAnalyticsResponse, error) +``` + + + + +### func \(\*BusinessClient\) CreateNewProductCatalog + +```go +func (client *BusinessClient) CreateNewProductCatalog() (string, error) +``` + + + + +### func \(\*BusinessClient\) DeleteUser + +```go +func (client *BusinessClient) DeleteUser(userId string) (string, error) +``` + + + + +### func \(\*BusinessClient\) Fetch + +```go +func (client *BusinessClient) Fetch() FetchBusinessAccountResponse +``` + + + + +### func \(\*BusinessClient\) FetchAllProductCatalogs + +```go +func (client *BusinessClient) FetchAllProductCatalogs() (string, error) +``` + + + + +### func \(\*BusinessClient\) FetchAnalytics + +```go +func (client *BusinessClient) FetchAnalytics(options AccountAnalyticsOptions) +``` + + + + +### func \(\*BusinessClient\) GetBusinessId + +```go +func (bc *BusinessClient) GetBusinessId() string +``` + + + + +### func \(\*BusinessClient\) SetBusinessId + +```go +func (bc *BusinessClient) SetBusinessId(id string) +``` + + + + +### func \(\*BusinessClient\) UpdateUser + +```go +func (client *BusinessClient) UpdateUser(userId string, tasks []BusinessRole) (string, error) +``` + + + + +## type BusinessClientConfig + + + +```go +type BusinessClientConfig struct { + BusinessAccountId string `json:"businessAccountId" validate:"required"` + AccessToken string `json:"accessToken" validate:"required"` + Requester *request_client.RequestClient +} +``` + + +## type BusinessRole + + + +```go +type BusinessRole string +``` + + + +```go +const ( + BusinessRoleManage BusinessRole = "MANAGE" + BusinessRoleDevelop BusinessRole = "DEVELOP" + BusinessRoleManageTemplates BusinessRole = "MANAGE_TEMPLATES" + BusinessRoleManagePhone BusinessRole = "MANAGE_PHONE" + BusinessRoleViewCost BusinessRole = "VIEW_COST" + BusinessRoleManageExtensions BusinessRole = "MANAGE_EXTENSIONS" + BusinessRoleViewPhoneAssets BusinessRole = "VIEW_PHONE_ASSETS" + BusinessRoleManagePhoneAssets BusinessRole = "MANAGE_PHONE_ASSETS" + BusinessRoleViewTemplates BusinessRole = "VIEW_TEMPLATES" + BusinessRoleMessaging BusinessRole = "MESSAGING" + BusinessRoleManageBusinessPhones BusinessRole = "MANAGE_BUSINESS_PHONES" +) +``` + + +### func \(\*BusinessRole\) String + +```go +func (role *BusinessRole) String() string +``` + + + + +## type ConversationAnalyticsGranularityType + + + +```go +type ConversationAnalyticsGranularityType string +``` + + + +```go +const ( + ConversationAnalyticsGranularityTypeHalfHour ConversationAnalyticsGranularityType = "HALF_HOUR" + ConversationAnalyticsGranularityTypeDay ConversationAnalyticsGranularityType = "DAILY" + ConversationAnalyticsGranularityTypeMonth ConversationAnalyticsGranularityType = "MONTHLY" +) +``` + + +## type ConversationAnalyticsOptions + + + +```go +type ConversationAnalyticsOptions struct { + Start time.Time `json:"start" validate:"required"` + End time.Time `json:"end" validate:"required"` + Granularity ConversationAnalyticsGranularityType `json:"granularity" validate:"required"` + PhoneNumbers []string `json:"phone_numbers,omitempty"` + + ConversationCategory []ConversationCategoryType `json:"conversation_category,omitempty"` + ConversationTypes []ConversationCategoryType `json:"conversation_types,omitempty"` + ConversationDirection []ConversationDirection `json:"conversation_direction,omitempty"` + Dimensions []ConversationDimensionType `json:"dimensions,omitempty"` +} +``` + + +## type ConversationCategoryType + + + +```go +type ConversationCategoryType string +``` + + + +```go +const ( + ConversationCategoryTypeAuthentication ConversationCategoryType = "AUTHENTICATION" + ConversationCategoryTypeMarketing ConversationCategoryType = "MARKETING" + ConversationCategoryTypeService ConversationCategoryType = "SERVICE" + ConversationCategoryTypeUtility ConversationCategoryType = "UTILITY" +) +``` + + +## type ConversationDimensionType + + + +```go +type ConversationDimensionType string +``` + + + +```go +const ( + ConversationDimensionTypeConversationCategory ConversationDimensionType = "CONVERSATION_CATEGORY" + ConversationDimensionTypeConversationDirection ConversationDimensionType = "CONVERSATION_DIRECTION" + ConversationDimensionTypeConversationType ConversationDimensionType = "CONVERSATION_TYPE" + ConversationDimensionTypeCountry ConversationDimensionType = "COUNTRY" + ConversationDimensionTypePhone ConversationDimensionType = "PHONE" +) +``` + + +## type ConversationDirection + + + +```go +type ConversationDirection string +``` + + + +```go +const ( + ConversationDirectionBusinessInitiated ConversationDirection = "BUSINESS_INITIATED" + ConversationDirectionUserInitiated ConversationDirection = "USER_INITIATED" +) +``` + + +## type ConversationType + + + +```go +type ConversationType string +``` + + + +```go +const ( + ConversationTypeFreeEntry ConversationType = "FREE_ENTRY" + ConversationTypeFreeTier ConversationType = "FREE_TIER" + ConversationTypeRegular ConversationType = "REGULAR" +) +``` + + +## type FetchBusinessAccountResponse + + + +```go +type FetchBusinessAccountResponse struct { + Id string `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + TimezoneId string `json:"timezone_id" validate:"required"` + MessageTemplateNamespace string `json:"message_template_namespace" validate:"required"` +} +``` + + +## type WhatsAppBusinessAccountAnalyticsProductType + + + +```go +type WhatsAppBusinessAccountAnalyticsProductType int +``` + + + +```go +const ( + WhatsAppBusinessAccountAnalyticsProductTypeNotificationMessages WhatsAppBusinessAccountAnalyticsProductType = 0 + WhatsAppBusinessAccountAnalyticsProductTypeCustomerSupportMessages WhatsAppBusinessAccountAnalyticsProductType = 2 +) +``` + + +## type WhatsAppConversationAnalyticsEdge + + + +```go +type WhatsAppConversationAnalyticsEdge struct { + Data []struct { + DataPoints []WhatsAppConversationAnalyticsNode `json:"data_points,omitempty"` + } `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` +} +``` + + +## type WhatsAppConversationAnalyticsNode + + + +```go +type WhatsAppConversationAnalyticsNode struct { + Start int `json:"start" validate:"required"` + End int `json:"end,omitempty" validate:"required"` + Conversation int `json:"conversation,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + Country string `json:"country,omitempty"` + ConversationType string `json:"conversation_type,omitempty"` + ConversationDirection string `json:"conversation_direction,omitempty"` + ConversationCategory string `json:"conversation_category,omitempty"` + Cost int `json:"cost,omitempty"` +} +``` + + +## type WhatsAppConversationAnalyticsResponse + + + +```go +type WhatsAppConversationAnalyticsResponse struct { + ConversationAnalytics []WhatsAppConversationAnalyticsEdge `json:"conversation_analytics" validate:"required"` +} +``` + + +## type WhatsappBusinessAccount + + + +```go +type WhatsappBusinessAccount struct { + BusinessVerificationStatus string `json:"business_verification_status,omitempty"` + Country string `json:"country,omitempty"` + Currency string `json:"currency,omitempty"` + IsTemplateAnalyticsEnabled string `json:"is_enabled_for_insights,omitempty"` + MessageTemplateNamespace string `json:"message_template_namespace,omitempty"` + Name string `json:"name,omitempty"` + OwnershipType string `json:"ownership_type,omitempty"` + PrimaryFundingId string `json:"primary_funding_id,omitempty"` + PurchaseOrderNumber string `json:"purchase_order_number,omitempty"` + TimezoneId string `json:"timezone_id,omitempty"` +} +``` + + +## type WhatsappBusinessAccountAnalyticsResponse + + + +```go +type WhatsappBusinessAccountAnalyticsResponse struct { + PhoneNumbers []string `json:"phone_numbers,omitempty"` + Granularity string `json:"granularity,omitempty"` + DataPoints []AnalyticsDataPoint `json:"data_points,omitempty"` +} +``` + diff --git a/docs/api-reference/pkg/client.mdx b/docs/api-reference/pkg/client.mdx index fa8936f..2163297 100644 --- a/docs/api-reference/pkg/client.mdx +++ b/docs/api-reference/pkg/client.mdx @@ -7,14 +7,16 @@ import "github.com/sarthakjdev/wapi.go/pkg/client" ## type Client -Client represents a WhatsApp client. + ```go type Client struct { - Media manager.MediaManager - Message manager.MessageManager - webhook manager.WebhookManager - phoneNumberId string + Business business.BusinessClient // Business is the business client. + Messaging []messaging.MessagingClient // MessagingClient is the messaging client. + eventManager *manager.EventManager // eventManager is the event manager. + webhook *manager.WebhookManager // webhook is the webhook manager. + requester *request_client.RequestClient + apiAccessToken string businessAccountId string } @@ -24,19 +26,10 @@ type Client struct { ### func New ```go -func New(configs ClientConfig) (*Client, error) +func New(config *ClientConfig) *Client ``` -NewWapiClient creates a new instance of Client. - - -### func \(\*Client\) GetPhoneNumberId -```go -func (client *Client) GetPhoneNumberId() string -``` - -GetPhoneNumberId returns the phone number ID associated with the client. ### func \(\*Client\) GetWebhookGetRequestHandler @@ -56,45 +49,46 @@ func (client *Client) GetWebhookPostRequestHandler() func(c echo.Context) error GetWebhookPostRequestHandler returns the handler function for handling POST requests to the webhook. - -### func \(\*Client\) InitiateClient + +### func \(\*Client\) Initiate ```go -func (client *Client) InitiateClient() bool +func (client *Client) Initiate() bool ``` InitiateClient initializes the client and starts listening to events from the webhook. It returns true if the client was successfully initiated. - -### func \(\*Client\) On + +### func \(\*Client\) NewMessagingClient ```go -func (client *Client) On(eventType events.EventType, handler func(events.BaseEvent)) +func (client *Client) NewMessagingClient(phoneNumberId string) *messaging.MessagingClient ``` -OnMessage registers a handler for a specific event type. - -### func \(\*Client\) SetPhoneNumberId + + +### func \(\*Client\) On ```go -func (client *Client) SetPhoneNumberId(phoneNumberId string) +func (client *Client) On(eventType events.EventType, handler func(events.BaseEvent)) ``` -SetPhoneNumberId sets the phone number ID for the client. +OnMessage registers a handler for a specific event type. ## type ClientConfig -ClientConfig represents the configuration options for the WhatsApp client. + ```go type ClientConfig struct { - PhoneNumberId string `validate:"required"` - ApiAccessToken string `validate:"required"` - BusinessAccountId string `validate:"required"` - WebhookPath string `validate:"required"` + BusinessAccountId string + ApiAccessToken string WebhookSecret string `validate:"required"` + + // these two are not required, because may be user want to use their own server + WebhookPath string WebhookServerPort int } ``` diff --git a/docs/api-reference/pkg/events.mdx b/docs/api-reference/pkg/events.mdx index 12506b9..5d160b0 100644 --- a/docs/api-reference/pkg/events.mdx +++ b/docs/api-reference/pkg/events.mdx @@ -101,7 +101,7 @@ type AudioMessageEvent struct { ### func NewAudioMessageEvent ```go -func NewAudioMessageEvent(baseMessageEvent BaseMessageEvent, mediaId string, audio components.AudioMessage, mime_type, sha256, mimeType string) *AudioMessageEvent +func NewAudioMessageEvent(baseMessageEvent BaseMessageEvent, audio components.AudioMessage, mimeType, sha256, mediaId string) *AudioMessageEvent ``` NewAudioMessageEvent creates a new AudioMessageEvent instance. @@ -138,11 +138,12 @@ type BaseMediaMessageEvent struct { ```go type BaseMessageEvent struct { - requester requestclient.RequestClient + requester request_client.RequestClient MessageId string `json:"message_id"` Context MessageContext `json:"context"` Timestamp string `json:"timestamp"` IsForwarded bool `json:"is_forwarded"` + PhoneNumber string `json:"phone_number"` } ``` @@ -150,7 +151,7 @@ type BaseMessageEvent struct { ### func NewBaseMessageEvent ```go -func NewBaseMessageEvent(messageId string, timestamp string, from string, isForwarded bool, requester requestclient.RequestClient) BaseMessageEvent +func NewBaseMessageEvent(phoneNumber string, messageId string, timestamp string, from string, isForwarded bool, requester request_client.RequestClient) BaseMessageEvent ``` @@ -268,7 +269,7 @@ type CustomerIdentityChangedEvent struct { ```go type CustomerNumberChangedEvent struct { - BaseMessageEvent `json:",inline"` + BaseSystemEvent `json:",inline"` ChangeDescription string `json:"changeDescription"` NewWaId string `json:"newWaId"` OldWaId string `json:"oldWaId"` diff --git a/docs/api-reference/pkg/messaging.mdx b/docs/api-reference/pkg/messaging.mdx new file mode 100644 index 0000000..4a30e58 --- /dev/null +++ b/docs/api-reference/pkg/messaging.mdx @@ -0,0 +1,96 @@ +```go +import "github.com/sarthakjdev/wapi.go/pkg/messaging" +``` + + + + +## type MessagingClient + +MessagingClient represents a WhatsApp client. + +```go +type MessagingClient struct { + Media manager.MediaManager + Message manager.MessageManager + PhoneNumberId string + ApiAccessToken string + BusinessAccountId string + Requester *request_client.RequestClient +} +``` + + +### func \(\*MessagingClient\) Deregister + +```go +func (client *MessagingClient) Deregister() (RegisterResponse, error) +``` + + + + +### func \(\*MessagingClient\) GetApiAccessToken + +```go +func (client *MessagingClient) GetApiAccessToken() string +``` + + + + +### func \(\*MessagingClient\) GetBusinessAccountId + +```go +func (client *MessagingClient) GetBusinessAccountId() string +``` + + + + +### func \(\*MessagingClient\) GetPhoneNumberId + +```go +func (client *MessagingClient) GetPhoneNumberId() string +``` + +GetPhoneNumberId returns the phone number ID associated with the client. + + +### func \(\*MessagingClient\) Register + +```go +func (client *MessagingClient) Register(pin string) (RegisterResponse, error) +``` + +this register function is for one time registration of the phone number to enable the usage with WhatsApp Cloud API + + +### func \(\*MessagingClient\) SetApiAccessToken + +```go +func (client *MessagingClient) SetApiAccessToken(apiAccessToken string) +``` + + + + +### func \(\*MessagingClient\) SetPhoneNumberId + +```go +func (client *MessagingClient) SetPhoneNumberId(phoneNumberId string) +``` + +SetPhoneNumberId sets the phone number ID for the client. + + +## type RegisterResponse + + + +```go +type RegisterResponse struct { + Success bool `json:"success"` +} +``` + diff --git a/docs/assets/.DS_Store b/docs/assets/.DS_Store new file mode 100644 index 0000000..34552b1 Binary files /dev/null and b/docs/assets/.DS_Store differ diff --git a/docs/assets/configure-webhook/.DS_Store b/docs/assets/configure-webhook/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/docs/assets/configure-webhook/.DS_Store differ diff --git a/docs/assets/configure-webhook/1.png b/docs/assets/configure-webhook/1.png new file mode 100644 index 0000000..c50163f Binary files /dev/null and b/docs/assets/configure-webhook/1.png differ diff --git a/docs/assets/configure-webhook/2.png b/docs/assets/configure-webhook/2.png new file mode 100644 index 0000000..5d168c5 Binary files /dev/null and b/docs/assets/configure-webhook/2.png differ diff --git a/docs/assets/configure-webhook/3.png b/docs/assets/configure-webhook/3.png new file mode 100644 index 0000000..5c4c658 Binary files /dev/null and b/docs/assets/configure-webhook/3.png differ diff --git a/docs/assets/configure-webhook/4.png b/docs/assets/configure-webhook/4.png new file mode 100644 index 0000000..1888bcf Binary files /dev/null and b/docs/assets/configure-webhook/4.png differ diff --git a/docs/assets/configure-webhook/cloudlfare.png b/docs/assets/configure-webhook/cloudlfare.png new file mode 100644 index 0000000..d302884 Binary files /dev/null and b/docs/assets/configure-webhook/cloudlfare.png differ diff --git a/docs/assets/create-meta-app/1.png b/docs/assets/create-meta-app/1.png new file mode 100644 index 0000000..36bd11b Binary files /dev/null and b/docs/assets/create-meta-app/1.png differ diff --git a/docs/assets/create-meta-app/2.png b/docs/assets/create-meta-app/2.png new file mode 100644 index 0000000..891aaff Binary files /dev/null and b/docs/assets/create-meta-app/2.png differ diff --git a/docs/assets/create-meta-app/3.png b/docs/assets/create-meta-app/3.png new file mode 100644 index 0000000..44aa1ea Binary files /dev/null and b/docs/assets/create-meta-app/3.png differ diff --git a/docs/assets/create-meta-app/4.png b/docs/assets/create-meta-app/4.png new file mode 100644 index 0000000..ca6be4c Binary files /dev/null and b/docs/assets/create-meta-app/4.png differ diff --git a/docs/assets/create-meta-app/5.png b/docs/assets/create-meta-app/5.png new file mode 100644 index 0000000..5bccd05 Binary files /dev/null and b/docs/assets/create-meta-app/5.png differ diff --git a/docs/assets/create-meta-app/6a.png b/docs/assets/create-meta-app/6a.png new file mode 100644 index 0000000..5bccd05 Binary files /dev/null and b/docs/assets/create-meta-app/6a.png differ diff --git a/docs/assets/create-meta-app/6b.png b/docs/assets/create-meta-app/6b.png new file mode 100644 index 0000000..a3a6d87 Binary files /dev/null and b/docs/assets/create-meta-app/6b.png differ diff --git a/docs/assets/create-meta-app/7a.png b/docs/assets/create-meta-app/7a.png new file mode 100644 index 0000000..72e7e01 Binary files /dev/null and b/docs/assets/create-meta-app/7a.png differ diff --git a/docs/assets/create-meta-app/7b.png b/docs/assets/create-meta-app/7b.png new file mode 100644 index 0000000..b4a5ddc Binary files /dev/null and b/docs/assets/create-meta-app/7b.png differ diff --git a/docs/guide/building-your-application/analytics.mdx b/docs/guide/building-your-application/analytics.mdx new file mode 100644 index 0000000..dd33909 --- /dev/null +++ b/docs/guide/building-your-application/analytics.mdx @@ -0,0 +1,3 @@ +--- +title: WhatsApp Business Account Analytics +--- \ No newline at end of file diff --git a/docs/guide/building-your-application/building-message-components.mdx b/docs/guide/building-your-application/building-message-components.mdx new file mode 100644 index 0000000..6e3e3d7 --- /dev/null +++ b/docs/guide/building-your-application/building-message-components.mdx @@ -0,0 +1,270 @@ +--- +title: Building Message Components +description: Build message components with ease using the Wapi.go SDK +--- + +Wapi.go SDK provides a simaple and easy to use classes architecture to build message components. You can build message component of following types using the Wapi.go SDK: + +- [Text Message](#text-message) +- [Image Message](#image-message) +- [Video Message](#video-message) +- [Audio Message](#audio-message) +- [Document Message](#document-message) +- [Location Message](#location-message) +- [Contact Message](#contact-message) +- [Reply Message](#reply-message) +- [Reaction Message](#reaction-message) +- [List Message](#list-message) +- [QuickReply ButtonMessage](#quickreply-buttonmessage) +- [Button Message](#button-message) +- [Product Message](#product-message) +- [Product List Message](#product-list-message) + +In all the media messages which includes image, document, audio, video and sticker, either we will have to use the Id of media, or a publicly accessible hosted media Url. + + With the rapidly changing whatsapp business platform features and offerings, we try our best to be in sync with the new features provided by the API. If you think we are missing upon any of the available type of message support. You can open a github issue [here](https://github.com/sarthakjdev/Wapi.go/issues) or direclty [contact](/guide/contact) the maintainer of the project. + +### Text Message + +Text message is the most basic message component that you can send to a user. You can create a text message using the following code: + +```typescript + +import { TextMessage } from '@wapijs/Wapi.go' + +const textMessage = new TextMessage({ + text: 'Please say "hello" to proceed.' +}) + +``` + +### Image Message + +Image message is a message component that you can use to send images to a user. You can create a image message using the following code: + +```typescript + +import { ImageMessage } from '@wapijs/Wapi.go' + +const imageMessage = new ImageMessage({ + link: 'https://example.com/image.jpg', + caption: 'This is a caption for the image.' +}) +``` + +### Video Message + +Video message is a message component that you can use to send videos to a user. You can create a video message using the following code: + +```typescript + +import { VideoMessage } from '@wapijs/Wapi.go' + +const videoMessage = new VideoMessage({ + link: 'https://example.com/video.mp4', + caption: 'This is a caption for the video.' +}) + +``` + +### Audio Message + +Audio message is a message component that you can use to send audio files to a user. You can create a audio message using the following code: + +```typescript + +import { AudioMessage } from '@wapijs/Wapi.go' + +const audioMessage = new AudioMessage({ + link: 'https://example.com/audio.mp3', +}) +``` + +### Document Message + +Document message is a message component that you can use to send documents to a user. You can create a document message using the following code: + +```typescript +import { DocumentMessage } from '@wapijs/Wapi.go' + +const documentMessage = new DocumentMessage({ + link: 'https://example.com/image.jpg', + caption: 'This is a caption for the image.' +}) + +``` + +### Location Message + +Location message is a message component that you can use to send location to a user. You can create a location message using the following code: + +```typescript +import { LocationMessage } from '@wapijs/Wapi.go' + +const locationMessage = new LocationMessage({ + latitude: 37.7749, + longitude: -122.4194, + name: 'San Francisco', + address: 'San Francisco, CA, USA' +}) +``` + +### Contact Message + +Contact message is a message component that you can use to send contact details to a user. You can create a contact message using the following code: + +In order to build a contact message, you need to use multiple classes provided to build, multiple components ofq the contact message. Now this has been done for ease of use and to provide a more structured way to build the contact message. + +```typescript +import { Contact, ContactMessage } from '@wapijs/Wapi.go' + +const contact = new Contact({ + name: { + last_name: 'Doe', + formatted_name: 'John Doe' + } + }) + + // optional + contact.addAddress({ + city: 'San Francisco', + country: 'USA', + street: '123 Main Street', + type: 'HOME', + country_code: 'US', + state: 'CA' + }) + + // optional + contact.addEmail({ + type: 'HOME', + email: 'sathak@softlancer.co' + }) + + // optional + contact.addPhone({ + type: 'CELL', + phone: '+1234567890' + }) + + // optional + contact.addUrl({ + type: 'HOME', + url: 'https://softlancer.co' + }) + + const contactMessage = new ContactMessage({ + contacts: [contact] + }) + + // add contact to the existing contact message + contactMessage.addContact(anotherContact) + +``` + +### Reaction Message + +Reaction message is a message component that you can use to send a reaction to a message. You can create a reaction message using the following code: + +```typescript +import { ReactionMessage } from '@wapijs/Wapi.go' +const reactionMessage = new ReactionMessage({ + reaction: '✅', + messageId: 'message-id' + }) +``` + +### List Message + +List message is a message component that you can use to send a list of items to a user. You can create a list message using the following code: + +```typescript +import { ListInteractionMessage } from '@wapijs/Wapi.go' + +const listMessage = new ListInteractionMessage({ + bodyText: 'Welcome to Wapi.go', + buttonText: 'Ask questions', + footerText: 'Beta version', + sections: [ + { + rows: [ + { + description: 'row description', + id: `row-1`, + title: `row title` + } + ], + title: 'section title' + } + ] + }) +``` + +### Button Interactive Message + +Button message is a message component that you can use to send a button to a user. You can create a button message using the following code: + +```typescript +import { ButtonInteractionMessage } from '@wapijs/Wapi.go' +const buttonMessage = new ButtonInteractionMessage({ + bodyText: 'Welcome to Wapi.go', + buttons: [{ + id: 'I am a button', + title: 'Click me' + }], + footerText: 'Beta version' +}) +``` + +### Product Message + +Product message is a message component that you can use to send a product to a user. You can create a product message using the following code: + +```typescript +import { ProductMessage } from '@wapijs/Wapi.go' +const productMessage = new ProductMessage({ + bodyText: 'Hii, I am a product.', + buttonText: 'Buy', + catalogId: '123', + productRetailerId: '123', + footerText: 'Beta version', +}) +``` + +### Product List Message + +Product List message is a message component that you can use to send a list of products to a user. You can create a product list message using the following code: + +```typescript +import { ProductListMessage, ProductListSection, Product, HeaderTypeEnum } from '@wapijs/Wapi.go' +const productListMessage = new ProductListMessage({ + bodyText: 'Welcome to Wapi.go', + buttonText: 'Buy', + footerText: 'Beta version', + catalogId: '123', + productRetailerId: '123', + header: { + text: 'Products', + type: HeaderTypeEnum.Text + }, + sections: [ + new ProductListSection([new Product('123')], 'Section 1'), + ] + }) + + const section = new ProductListSection([], 'Section 2') + + section.addProduct(new Product('123')) + + productListMessage.addSection(section) +``` + +### Temaplate Message + +Template message is a message component that you can use to send a template to a user. You can create a template message using the following code: + +You need to get your template approved before sending a template message to users. You can check the template message approval documentation [here](https://developers.facebook.com/docs/whatsapp/message-templates/guidelines/). + +```typescript +``` + diff --git a/docs/guide/building-your-application/event-handling.mdx b/docs/guide/building-your-application/event-handling.mdx new file mode 100644 index 0000000..e82fb0e --- /dev/null +++ b/docs/guide/building-your-application/event-handling.mdx @@ -0,0 +1,4 @@ +--- +title: Handle Events +description: Learn how to handle events using the Wapi.go SDK +--- diff --git a/docs/guide/building-your-application/handling-media.mdx b/docs/guide/building-your-application/handling-media.mdx new file mode 100644 index 0000000..a9dd566 --- /dev/null +++ b/docs/guide/building-your-application/handling-media.mdx @@ -0,0 +1,28 @@ +--- +title: Handling Media +description: Learn how to handle media messages using the Wapi.go SDK +--- + + +Officially Whatsapp supports uploading of media via API, getting Media Url from media Id and deleting the media. But the SDK as of now only supports the two features: + +- [Getting media Url from media Id](#getting-media-url-from-media-id) +- [Deleting the media](#deleting-media) + + +## Getting media Url from media Id + +You can get the media Url from media Id using the `getMediaUrl` method available on the [client](api-reference/classes/Client) class media manager. Here is an example of how you can get the media Url: + +```typescript + await whatsappClient.media.getUrl('mediaId') +``` + + +## Deleting media + +You can delete a media using the `deleteMedia` method available on the [client](api-reference/classes/Client) class media manager. Here is an example of how you can delete the media: + +```typescript + await whatsappClient.media.delete('mediaId') +``` \ No newline at end of file diff --git a/docs/guide/building-your-application/managing-business-account.mdx b/docs/guide/building-your-application/managing-business-account.mdx new file mode 100644 index 0000000..600f9cf --- /dev/null +++ b/docs/guide/building-your-application/managing-business-account.mdx @@ -0,0 +1,94 @@ +--- +title: Managing Business Account +--- + +Wapi.go SDK allows you to manage your business account with ease. You can manage your business account by using the methods and sub-business client available in the SDK. Here are some of the features that you can do with the SDK: + +### Fetch Business Account + +Check the api reference docs [here](/api-reference/pkg/business) to know about all available methods + +To fetch business account, you need to cal the `Fetch` method available on the `Business` client attached to the main wapi.go client. The method will return the business account details. + +```go +business, err := wapi.Business.Fetch() +``` + +### Fetch Business Account Statistics + +#### Account Analytics + +Use the following method on Business client to fetch the account analytics. + +```go + client.Business.FetchAnalytics(business.AccountAnalyticsOptions{ + Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + End: time.Now(), + Granularity: business.AnalyticsRequestGranularityTypeDay, + }) +``` + +Check the api reference docs [here](/api-reference/pkg/business#type-AccountAnalyticsOptions) to know about all available options to tweak the response type and scope for business account analytics. + +#### Conversation Statistics + +Use the following method on Business client to fetch the conversation statistics. + +```go + client.Business.ConversationAnalytics(business.ConversationAnalyticsOptions{ + Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + End: time.Now(), + Granularity: business.ConversationAnalyticsGranularityTypeDay, + }) +``` + +Check the api reference docs [here](/api-reference/pkg/business#type-conversationanalyticsoptions) to know about all available options to tweak the response type and scope for business account conversation analytics. + + +### Fetch Business Account Phone Numbers, Migrate New and Update Phone Numbers + +Visit this guide [here](/guide/building-your-application/managing-phone-numbers) + to know more about the account phone numbers operations. + + +### Fetch Business Account Templates and Create New Templates or Update Existing Templates + +Visit this guide [here](/guide/building-your-application/managing-message-templates) + to know more about the account messaging templates operations. + + +### Updating a user + +Use the following method on Business client to fetch the conversation statistics. + +```go + client.Business.ConversationAnalytics(business.ConversationAnalyticsOptions{ + Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + End: time.Now(), + Granularity: business.ConversationAnalyticsGranularityTypeDay, + }) +``` + +Check the api reference docs [here](/api-reference/pkg/business#type-conversationanalyticsoptions) to know about all available options to tweak the response type and scope for business account conversation analytics. + + +### Deleting a user access form Whatsapp Business Account + +Use the following method on Business client to fetch the conversation statistics. + +```go + client.Business.ConversationAnalytics(business.ConversationAnalyticsOptions{ + Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + End: time.Now(), + Granularity: business.ConversationAnalyticsGranularityTypeDay, + }) +``` + +Check the api reference docs [here](/api-reference/pkg/business#type-conversationanalyticsoptions) to know about all available options to tweak the response type and scope for business account conversation analytics. + + +### Fetch a Product Catalog + + +### Delete a Product Catalog + diff --git a/docs/guide/building-your-application/managing-message-templates.mdx b/docs/guide/building-your-application/managing-message-templates.mdx new file mode 100644 index 0000000..ce579e4 --- /dev/null +++ b/docs/guide/building-your-application/managing-message-templates.mdx @@ -0,0 +1,70 @@ +--- +title: Manage Message Templates +description: 'Create, update and delete message templates programmatically.' +--- + +Check the api reference docs [here](/api-reference/internal/manager#type-templatemanager) to know about all available methods + + +Wapi.go SDK allows you to manage your business account message templates with ease. You can manage your business account message templates by using the methods and sub-business client available in the SDK. Here are some of the features that you can do with the SDK: + +### Fetch All Message Templates + +```go +client.Business.Template.FetchAll() +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-fetchall) to know about this method + +### Fetch Message Template by ID + +```go +client.Business.Template.Fetch("") +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-fetch) to know about this method + +### Fetch Analytics of Message Templates + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-fetchanalytics) to know about this method + + +```go +client.Business.Template.FetchAnalytics() +``` + +### Fetch Performance Analytics of Message Templates + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-fetchperformanceanalytics) to know about this method + + +```go +client.Business.Template.FetchPerformanceAnalytics() +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-fetchperformanceanalytics) to know about this method + +### Create New Message Template + +```go +client.Business.Template.Create(manager.WhatsappMessageTemplateCreateRequestBody{}) +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-create) to know about this method + +### Update Existing Message Template + +```go +client.Business.Template.Update("", manager.WhatsAppBusinessAccountMessageTemplateUpdateRequestBody{}) +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-update) to know about this method + +### Delete Message Template + +```go +client.Business.Template.Delete("") +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-templatemanager-delete) to know about this method + diff --git a/docs/guide/building-your-application/managing-phone-numbers.mdx b/docs/guide/building-your-application/managing-phone-numbers.mdx new file mode 100644 index 0000000..4f8cad3 --- /dev/null +++ b/docs/guide/building-your-application/managing-phone-numbers.mdx @@ -0,0 +1,69 @@ +--- +title: Managing Phone Numbers +description: 'Manage your business account phone numbers. Migrate and Get Phone Statuses with in-depth analytics like quality signals.' +--- + +Check the api reference docs [here](/api-reference/internal/manager#type-phonenumbermanager) to know about all available methods + + +Wapi.go SDK allows you to manage your business account phone numbers with ease. You can manage your business account phone numbers by using the methods and sub-business client available in the SDK. Here are some of the features that you can do with the SDK: + +### Fetch All Phone Numbers + +```go +client.Business.PhoneNumber.FetchAll(manager.FetchPhoneNumberFilters{ + GetSandboxNumbers: false, +}) +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-fetchall) to know about this method + +### Fetch Phone Number by ID + +```go +client.Business.PhoneNumber.Fetch("") +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-fetch) to know about this method + +### Generate QR code for Phone Number + +```go +client.Business.PhoneNumber.GenerateQrCode("", "This is Wapi.go this side.") +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-generateqrcode) to know about this method + +### Fetch all QR code for Phone Number + +```go +client.Business.PhoneNumber.GetAllQrCodes("Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-getallqrcodes) to know about this method + +### Delete a QR code + +```go +client.Business.PhoneNumber.DeleteQrCode("") +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-deleteqrcode) to know about this method + +### Get QR code by Id + +```go +client.Business.PhoneNumber.GetQrCodeById("") +``` + + +Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-getqrcodebyid) to know about this method + +### Update QR Code + +```go +client.Business.PhoneNumber.UpdateQrCode("", "This is Wapi.go this side.") +``` + +Check the api reference docs [here](/api-reference/internal/manager#func-phonenumbermanager-updateqrcode) to know about this method \ No newline at end of file diff --git a/docs/guide/building-your-application/replying-and-reacting.mdx b/docs/guide/building-your-application/replying-and-reacting.mdx new file mode 100644 index 0000000..fc91ec2 --- /dev/null +++ b/docs/guide/building-your-application/replying-and-reacting.mdx @@ -0,0 +1,61 @@ +--- +title: Replying and Reacting to messages +description: Learn how to reply and react to messages using the Wapi.go SDK +--- + + +## Replying to Messages + +You can reply to a messsage via event listerner handler. + +```typescript + +// assuming you already have initiated the client, +// you can do the following to reply to incoming messages + + +// in the similar way, any incoming message event, +// which is not a System event can be replied back to. +``` + + +## Reacting to Messages + +There are mainly two ways you can react to a message: + +### Via Incoming Message Event + +You can listen to incoming messages and reply to them using the `on` method available on the [client](api-reference/classes/Client) event. Here is an example of how you can listen to incoming messages and react to them: + +```typescript + +// assuming you already have initiated the client, +// you can do the following to reply to incoming messages + + +// in the similar way, any incoming message event, +// which is not a System event can be replied back to. +``` + + +### Via Sending a Reaction message + +You can also send a message of type `Reaction` to a message. Here is an example of how you can send a reaction a message: + +```typescript + +// assuming you already have initiated the client, + +const replyMessageComponent = new ReplyMessage({ + message: new TextMessage({ + text: 'This is a reply.' + }) +}) + +// send a reaction message +await whatsappClient.message.send({ + message: replyMessageComponent, + phoneNumber: '1234567890', +}) + +``` \ No newline at end of file diff --git a/docs/guide/building-your-application/sdk-architecture.mdx b/docs/guide/building-your-application/sdk-architecture.mdx new file mode 100644 index 0000000..0429d88 --- /dev/null +++ b/docs/guide/building-your-application/sdk-architecture.mdx @@ -0,0 +1,70 @@ +--- +title: Wapi.go SDK Architecture +--- + + +Wapi.go has a distributed architecture that is designed to incorporate both the Business Management API as wel as the Cloud API porvided by the WhatsApp Business Platform. The architecture is designed to be modular and extensible, allowing developers to easily add new features and functionalities to the SDK. The architecture is divided into the following components: + + +- Main Top level client with webhook server +- Business Management API client +- Messaging Client (Cloud API client) + + + +## Main Top level client with webhook server + +The main top level client is the entry point for the SDK. It is responsible for handling incoming messages from the WhatsApp Business Platform and any other event notification to which you have made your application at the developer platform to subscribed to. In order, to start using the SDK you must have to create a instance of this client with the following code: + +```go +client := wapi.New(&wapi.ClientConfig{ + ApiAccessToken: "", + BusinessAccountId: "", + WebhookPath: "/webhook", + WebhookSecret: "", + WebhookServerPort: 8080, +}) +``` + +## Buiness Management API client + +Now, in order to access the business account specific features such as managing phone numbers, template messages, accessing analytics and updating other entities, you need to access the instance of business client already initiated in the main client. The business client is responsible for handling all the business management API requests. You can access the business client instance by calling the following code: + +```go + +businessClient := client.Business + +// suppose you want to fetch the conversation analytics data points, then the request would look like: + +response, err := client.Business.ConversationAnalytics(business.ConversationAnalyticsOptions{ + Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + End: time.Now(), + Granularity: business.ConversationAnalyticsGranularityTypeDay, +}) + +if err != nil { + log.Fatal(err) +} +``` + +## Messaging Client (Cloud API client) + +To access the messaging client you need to create a instance of the messaging client by calling the following code: + +```go +messagingClient := client.NewMessagingClient("") + +// you can further access the message manager and send messages to the user by calling the send method availabel in the message manager instance. + +messagingClient.Message.send("message component here") + + +// you can also manage the media of your phone number using + +messageClient.Media.GetMediaUrl("") + +``` + + + +The decision behind initiating the business client in advance on the top client instance and not the messaging client because a main client can have multiple messaging clients each for a distinct phone number but not multiple business client. diff --git a/docs/guide/building-your-application/sending-messages.mdx b/docs/guide/building-your-application/sending-messages.mdx new file mode 100644 index 0000000..8e049a6 --- /dev/null +++ b/docs/guide/building-your-application/sending-messages.mdx @@ -0,0 +1,32 @@ +--- +title: Sending Messages +description: Learn how to send messages using the Wapi.go SDK +--- + +Check the architecture [guide](/guide/building-your-application/sdk-architecture) once before moving ahead with this send message guide. + +Wapi.go SDK provides a simple and easy to use classes architecture to send messages. You can send message of following types using the Wapi.go SDK: + +## Send a message + +You can send a message to a user using the `send` method available on the messaging client on main client cloud api manager. Here is an example of how you can send a message: + + + +```go +// assuming you already have initiated the client +const textMessage = new TextMessage({ + text: 'Hello, how are you?' +}) + +// send a message +const response = await whatsappClient.message.send({ + to: '919876543210', + message: textMessagew +}) +``` + +The .send() method is a asynchronous method and returns a promise of response which can either be a success response or a error response. It is suggested to acknowledge the response, as calling the .send() method does not gurantees the message has been sent, there may be some API error which may lead to message not being sent, the SDK does not retries the message, it is the application login responsibility to add retry mechanism. However, if you think this should be added to the SDK itlsef, you are invited to open a issue on the github repository of the SDK. + + + diff --git a/docs/guide/building-your-application/using-custom-server.mdx b/docs/guide/building-your-application/using-custom-server.mdx new file mode 100644 index 0000000..e69de29 diff --git a/docs/guide/building-your-application/using-inbuild-webhook-server.mdx b/docs/guide/building-your-application/using-inbuild-webhook-server.mdx new file mode 100644 index 0000000..e69de29 diff --git a/docs/guide/contact.mdx b/docs/guide/contact.mdx new file mode 100644 index 0000000..b638cd1 --- /dev/null +++ b/docs/guide/contact.mdx @@ -0,0 +1,12 @@ +--- +Title: Contacts +Description: You can contact us using the following methods +--- + +The prefered way to contact us by email at sarthak@softlancer.co. + +But you can also reach out to us here: + +- Twitter/X: @sarthakjdev or @softlancerhq +- Linkedin: https://www.linkedin.com/in/sarthakjdev/ +- Github: https://www.github.com/sarthakjdev \ No newline at end of file diff --git a/docs/guide/contributing.mdx b/docs/guide/contributing.mdx new file mode 100644 index 0000000..5c9a7cb --- /dev/null +++ b/docs/guide/contributing.mdx @@ -0,0 +1,35 @@ +--- +title: Contributing to Wapi.go +description: Checkout the ways you can contribute to Wapi.go +--- + +Wapi.go is an open source project and we welcome contributions from the community. There are many ways you can contribute to the project: + +- Reporting bugs +- Suggesting new features +- Writing documentation +- Submitting pull requests +- Helping others in the community +- Sharing the project with others + + +## Reporting bugs + +If you find a bug in the project, please report it by creating a new issue in the project's GitHub repository. Make sure to include as much information as possible, including the steps to reproduce the bug and the expected behavior. + +## Suggesting new features + +If you have an idea for a new feature that you would like to see in the project, please create a new issue in the project's GitHub repository. Make sure to include a detailed description of the feature and why you think it would be beneficial to the project. + + +## Writing documentation + +Documentation is an important part of any project, and we welcome contributions to the project's documentation. If you find any errors or omissions in the documentation, please create a new issue in the project's GitHub repository. You can also submit a pull request with the changes you would like to see. + +## Submitting pull requests + +If you would like to contribute code to the project, you can do so by submitting a pull request. Make sure to follow the project's coding standards and guidelines, and include a detailed description of the changes you are proposing. + +## Helping others in the community + +If you are experienced with the project and would like to help others in the community, you can do so by answering questions on the project's GitHub repository or forum. Sharing your knowledge and expertise with others is a great way to contribute to the project. \ No newline at end of file diff --git a/docs/guide/installation-and-preparations/creating-application.mdx b/docs/guide/installation-and-preparations/creating-application.mdx new file mode 100644 index 0000000..414135e --- /dev/null +++ b/docs/guide/installation-and-preparations/creating-application.mdx @@ -0,0 +1,23 @@ +--- +title: 'Setting up Wapi.go Application Development Environment' +--- + + +## Initiating a Wapi.go project + +### Chat bot + +The first step to build a Wapi.go based chat bot is to create a new project. You can do this by running the following command: + +```go +go mod init +go get github.com/wapichat/wapi.go +``` + +### Other use cases + +- You are a developer and want to integrate the whatsapp business API with your existing backend. +- You are a solution partner or a tech partner with WhatsApp and want to access the WhatsApp business API to build a solution for your clients. + + Process to building the application [docs](/guide/building-your-application), for further assistance. + diff --git a/docs/guide/installation-and-preparations/setup-golang.mdx b/docs/guide/installation-and-preparations/setup-golang.mdx new file mode 100644 index 0000000..452569d --- /dev/null +++ b/docs/guide/installation-and-preparations/setup-golang.mdx @@ -0,0 +1,15 @@ +--- +title: Setting up Golang Environment +--- + + + + +To get started with Wapi.go, you need to have Golang installed on your machine. If you don't have Golang installed, you can download it from the official website [here](https://go.dev/doc/install). + + + + + + + diff --git a/docs/guide/introduction.mdx b/docs/guide/introduction.mdx index 8c76bd6..88fc2ca 100644 --- a/docs/guide/introduction.mdx +++ b/docs/guide/introduction.mdx @@ -10,13 +10,13 @@ title: Introduction ## What is Wapi.go ? -Wapi.go is a golang SDK that helps developers to build whatsapp business API based app with ease and faster. You can build chat bots or integrate the SDK with you existing backend to enable whatsapp based communication in your application. +Wapi.go is a node.js SDK that helps developers to **build whatsapp business API based app** with ease and faster. You can **build chat bots or integrate the SDK with you existing backend** to enable whatsapp based communication in your application. ## What problems does it solves ? - Whatsapp business API is a powerful tool to enable communication with your customers. But it is not easy to build a chat bot or integrate the API with your existing backend. -- Wapi.go solves this problem by providing a simple and easy to use SDK that helps you to build chat bots or integrate the API with your existing backend. +- Wapi.go solves this problem by providing a simple and easy to use SDK that helps you to build chat bots or integrate the API with your existing backend. - Wapi.go provides a in-depth abstraction over whatsapp business api and provides a simple and easy to use API to interact with the whatsapp business API. @@ -27,19 +27,25 @@ Wapi.go is a golang SDK that helps developers to build whatsapp business API bas title="Listen to incoming notifiations" icon="pen-to-square" > - Get your docs set up locally for easy development + Listen to incoming messages, status updates and other notifications from whatsapp business API. - Preview your changes before you push to make sure they're perfect + Manage Account, template messages, phone numbers and even fetch analytics seamlessly. + + + Wapi.go enables you to create multiple messaging client with different phone numbers to send and recieve messages. - Preview your changes before you push to make sure they're perfect + You do not need to worry of complex API payload structure anymore. Just use the simple to construct classes and methods. diff --git a/docs/guide/quickstart.mdx b/docs/guide/quickstart.mdx index e69de29..9247ab6 100644 --- a/docs/guide/quickstart.mdx +++ b/docs/guide/quickstart.mdx @@ -0,0 +1,68 @@ +--- +title: Quick Start Guide +description: 'Welcome to the home of your new documentation' +--- + +## + + + + + Checkout the typescript code API reference documentation here. + + + Checkout the available events and how to listen to them + + + Build interactive features and designs to guide your users + + + Manage your business account with ease. + + + Create, update and delete message templates programmatically. + + + Migrate and Get Phone Statuses with in-depth analytics like quality signals. + + + Fetch account analytics like message sent, recieved, read and more with ease. + + + Check out the example chat bot to get inspiration + + + + diff --git a/docs/guide/references.mdx b/docs/guide/references.mdx new file mode 100644 index 0000000..cd82f9b --- /dev/null +++ b/docs/guide/references.mdx @@ -0,0 +1,20 @@ +--- +title: Important Reference Links +--- + + +- [Official Whatsapp Business Platform Docs](https://developers.facebook.com/docs/whatsapp/) +- [Sunset of On-Premises API](https://developers.facebook.com/docs/whatsapp/on-premises/sunset) +- [Official WhatsApp Business Account Management API Docs](https://developers.facebook.com/docs/whatsapp/business-management-api) + - [API Reference](https://developers.facebook.com/docs/whatsapp/business-management-api/reference) + - [Setting Up Business Management API webhook](https://developers.facebook.com/docs/whatsapp/business-management-api/guides/set-up-webhooks) + - [Error Codes](https://developers.facebook.com/docs/whatsapp/business-management-api/error-codes) +- [Official WhatsApp Cloud API Docs](https://developers.facebook.com/docs/whatsapp/cloud-api) + - [Webhook Components](https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components) + - [Setting Up Cloud API Webhooks](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/set-up-webhooks) + - [Message Reference](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages) +- [Official WhatsApp On-Premises API Docs](https://developers.facebook.com/docs/whatsapp/on-premises) +- [Migrating from On-Premises API to Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/migrating-from-onprem-to-cloud/) +- [WhatsApp Business Platform Development Changelog](https://developers.facebook.com/docs/whatsapp/business-platform/developer-onboarding-changelog) +- [WhatsApp Business Platform for Tech Providers](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started-for-tech-providers) +- [WhatsApp Business Platform for Solution Providers](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started-for-solution-partners) \ No newline at end of file diff --git a/docs/guide/wapijs-sdk-scope.mdx b/docs/guide/wapijs-sdk-scope.mdx new file mode 100644 index 0000000..968982f --- /dev/null +++ b/docs/guide/wapijs-sdk-scope.mdx @@ -0,0 +1,16 @@ +--- +title: Scope of Wapi.go SDK +--- + +## Current Status + + + Currently, Wapi.go SDK support building almost all the type of message compoennt and sending them to customers. Listening to incoming event notification including user triggered and system generated events. + + +## Upcoming Features + +- Support for [Interactive Address Message](https://developers.facebook.com/docs/whatsapp/cloud-api/messages/address-messages) +- Support for WhatsApp Payments API +- Support for WhatsApp Order Mangement +- Support for [Interactive Location Message](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages/location-request-messages) \ No newline at end of file diff --git a/docs/guide/whatsapp-api-setup/configuring-webhook.mdx b/docs/guide/whatsapp-api-setup/configuring-webhook.mdx new file mode 100644 index 0000000..65a5ab2 --- /dev/null +++ b/docs/guide/whatsapp-api-setup/configuring-webhook.mdx @@ -0,0 +1,96 @@ +--- +title: Configuring Webhook +description: Learn how to configure the webhook for your Whatsapp Business Account +--- + + +To receive messages from your customers, you need to configure a webhook for your Whatsapp Business Account. The webhook is a URL that you provide to Whatsapp to send messages to your application. + + +## Configuring Webhook for local development setup + + +### Getting a public URL for your local development setup + +You may sometimes face an error on the whatsapp webhook management dashboard saying that the URL is malicious in case when you are using NGROK, in that case you must go ahead with using cloudflare tunnel. + +- [Using NGROK](#ngrok) +- [Using Cloudflare Tunnel](#cloudflare-tunnel) + + +#### NGROK + +If you are developing your application locally, you can use [ngrok](https://ngrok.com/) to expose your local server to the internet. + +To use ngrok, follow these steps: + +1. Download and install ngrok from [here](https://ngrok.com/download). +2. Run the following command in your terminal to expose your local server to the internet: + +```bash +ngrok http http://localhost: +``` +3. You will see something like this in your terminal: + + + + +Make sure to replace `` with the port number on which your application is running. + + +#### Cloudflare Tunnel + +To use cloudflare tunnel, follow these steps: + +1. Download and install cloudflare tunnel from [here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation). +2. Run the following command in your terminal to expose your local server to the internet: + +```bash +cloudflared tunnel --url http://localhost: +``` + +3. You will see something like this in your terminal: + + + + +Make sure to replace `` with the port number on which your application is running. + + +### Submitting URL to whatsapp business platform + +Follow the steps below to submit your webhook URL to the Whatsapp Business Platform and enable incoming events routing to your local development setup: + + + + + + Visit your application dashboard via [Meta Developer Portal](https://developers.facebook.com/apps) + + + + + + + + + + + + Webhook secret is the same string which you have used in your Wapi.go Application Client. + + + + + + + + Make sure your [Wapi.go local application](/guide/building-your-application/creating-wapi-client) is up and running before proceeding with verify and save process. + + + + + + + + diff --git a/docs/guide/whatsapp-api-setup/creating-business-app.mdx b/docs/guide/whatsapp-api-setup/creating-business-app.mdx new file mode 100644 index 0000000..65d0835 --- /dev/null +++ b/docs/guide/whatsapp-api-setup/creating-business-app.mdx @@ -0,0 +1,67 @@ +--- +title: Create a Meta Business App +--- + +You need to create a application in your Meta Developer Account in order to get access to WhatsApp Business API Products. + +Create your account on [Meta Development Hub](https://developers.facebook.com), if you do not have one yet. + + + + + + a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You can select the Facebook page of your business from the `Business Portfolio` dropdown. + + + + + + + + + + + + + + + + + + + + + You will be able to get a permanent access token once you get your WhatsApp Business Account verified. + + + \ No newline at end of file diff --git a/docs/guide/whatsapp-api-setup/getting-api-key.mdx b/docs/guide/whatsapp-api-setup/getting-api-key.mdx new file mode 100644 index 0000000..02be78a --- /dev/null +++ b/docs/guide/whatsapp-api-setup/getting-api-key.mdx @@ -0,0 +1,13 @@ +--- +title: Getting API Key for WhatsApp Cloud Platform +--- + +WhatsApp provides multiple type of access token to support their ever expanding business offering like Solution providers, Tech Partners and direct custoemrs. + +Initially as long as your app is in development mode, you will be able to get a temporary access token to start building your application which is valid for 24 hours only and can be used to test your application. + +To get the temporary access token, you can visit your Meta Application dashboard and click on the `API Setup` under `WhatsApp` dropdown from the sidebar. You can check out the guide [here](/guide/whatsapp-api-setup/creating-business-app#get-your-temporary-api-key-test-phone-number-text-phone-number-id-and-whatsapp-business-account-id) in the last few steps we have already discusses the same there. + +In order to get your permanent access token, you need to get your WhatsApp Business Account verified. Once your account is verified, you will be able to get the permanent access token from the same place where you got the temporary access token. + +**You can checkout the offical WhatsApp documentation on how to get a permanent access token [here](https://developers.facebook.com/docs/whatsapp/business-management-api/get-started#1--acquire-an-access-token-using-a-system-user-or-facebook-login)** diff --git a/docs/guide/whatsapp-api-setup/understanding-whatsapp-business-platform.mdx b/docs/guide/whatsapp-api-setup/understanding-whatsapp-business-platform.mdx new file mode 100644 index 0000000..6840a83 --- /dev/null +++ b/docs/guide/whatsapp-api-setup/understanding-whatsapp-business-platform.mdx @@ -0,0 +1,38 @@ +--- +title: WhatsApp Business Platform +description: Understand how the whatsapp business platform works and what can you use to build your own application via the API products whatsapp offers +--- + +Whatsapp Business Platform offers majorly three API products to enable developers to build their own applications. These API products are: + +- WhatsApp Business Account Management API +- WhatsApp On-Premises API +- WhatApp Cloud API + +## WhatsApp Business Account Management API + + +You can read the official WhatsApp Business Management API documentation here. + + +This API offers a suite of API endpoints which help you manage the business account, manage the business account settings, manage phone numbers and message templates. +As of now, the Wapi.go SDK do not supports this API, but we are working on it and will soon release the support for this API. + +## WhatsApp On-Premises API + + +You can read the official WhatsApp On-Premises documentation here. + + +On-Premises API was a legacy API offered by WhatsApp which requires self-hosting by the users, but it will soon be deprecated and hence users are advised to moved to new WhatsApp Cloud API. +You can read the official sunsetting docs of On-Premises API [here](https://developers.facebook.com/docs/whatsapp/on-premises/sunset). Also, read the offifical on-prem API to Cloud API migration guide [here](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/migrating-from-onprem-to-cloud/). + +## WhatsApp Cloud API + + +You can read the official WhatsApp Cloud API documentation here. + + +This is a newly introduced API by WhatsApp which is cloud hosted and managed by WhatsApp itself. This API offers a suite of API endpoints which help you send messages, webhooks for incoming messages notification, send media messages, send interactive messages and much more. + +And this is what Wapi.go SDK majorly focusses on right now, to help developers build their own applications using this API. Wapi.go provides a smart layer of abstraction around this API, which helps developer to build application easily. Developers can easily build chat bots or integrate the SDK in their exisiting appplication using the robust infrastructure for sending and event listening for messages. \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index ff571b5..a2b4a60 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -28,8 +28,8 @@ }, "topbarLinks": [ { - "name": "Support", - "url": "sarthak@softlancer.co" + "name": "Report Bugs", + "url": "https://github.com/sarthakjdev/wapi.go/issues/new" } ], "topbarCtaButton": { @@ -44,7 +44,7 @@ ], "anchors": [ { - "name": "Star on GitHub", + "name": "Star us on GitHub", "icon": "github", "url": "https://github.com/sarthakjdev/wapi.go" }, @@ -56,20 +56,62 @@ { "name": "Community", "icon": "slack", - "url": "https://slack.com/wapikit" + "url": "https://join.slack.com/t/wapikit/shared_invite/zt-2kl7eg29s-4DfP9lFwojQg_yCcyW_w6Q" } ], "navigation": [ { "group": "Guide", + "iconType": "regular", "pages": ["guide/introduction", "guide/quickstart"] }, + { + "group": "Installation and Preparations", + "pages": [ + "guide/installation-and-preparations/setup-golang", + "guide/installation-and-preparations/creating-application" + ] + }, + { + "group": "Whatsapp API setup", + "pages": [ + "guide/whatsapp-api-setup/understanding-whatsapp-business-platform", + "guide/whatsapp-api-setup/creating-business-app", + "guide/whatsapp-api-setup/getting-api-key", + "guide/whatsapp-api-setup/configuring-webhook" + ] + }, + { + "group": "Building your application", + "pages": [ + "guide/building-your-application/sdk-architecture", + "guide/building-your-application/managing-business-account", + "guide/building-your-application/managing-message-templates", + "guide/building-your-application/managing-phone-numbers", + "guide/building-your-application/building-message-components", + "guide/building-your-application/sending-messages", + "guide/building-your-application/event-handling", + "guide/building-your-application/replying-and-reacting", + "guide/building-your-application/handling-media" + ] + }, + { + "group": "Others", + "pages": [ + "guide/wapijs-sdk-scope", + "guide/references", + "guide/contributing", + "guide/contact" + ] + }, { "group": "Packages Documentation", "pages": [ - "api-reference/pkg/client", + "api-reference/pkg/messaging", "api-reference/pkg/components", - "api-reference/pkg/events" + "api-reference/pkg/events", + "api-reference/pkg/business", + "api-reference/internal/manager" ] } ], diff --git a/examples/chat-bot/main.go b/examples/chat-bot/main.go index 4628e59..6ad591a 100644 --- a/examples/chat-bot/main.go +++ b/examples/chat-bot/main.go @@ -3,7 +3,9 @@ package main import ( "fmt" "strings" + "time" + "github.com/sarthakjdev/wapi.go/pkg/business" wapi "github.com/sarthakjdev/wapi.go/pkg/client" wapiComponents "github.com/sarthakjdev/wapi.go/pkg/components" "github.com/sarthakjdev/wapi.go/pkg/events" @@ -11,27 +13,47 @@ import ( func main() { // creating a client - whatsappClient, err := wapi.New(wapi.ClientConfig{ - PhoneNumberId: "", - ApiAccessToken: "", - BusinessAccountId: "", + + businessAccountId := "103043282674158" + phoneNumber := "113269274970227" + + client := wapi.New(&wapi.ClientConfig{ + ApiAccessToken: "EABhCftGVaeIBOZCZANWI9Tkuy3etYh4lWP1nk1bqcuSyboHi5B1DDj1H3Q4dGYxK9iJ5f6U9Pb1BvoeTTR3aDCVtJIud10aUAtdl7YNbEqH2qeOLBZCEIZAFyt0mSDzog5dVcQHWDDPz1JQmNuebpFIJaBqqcxDdKNdCgx7AQGptJYhPclGc8E9T68Em5dThClm2ZAOST4kVIcvH2dA8zx9kZCqlAevUZBTxaB5hLuS18sZD", + BusinessAccountId: businessAccountId, WebhookPath: "/webhook", WebhookSecret: "1234567890", WebhookServerPort: 8080, }) - if err != nil { - fmt.Println("error creating client", err) - return - } + // messagingClient := client.NewMessagingClient("113269274970227") + + // client.Business.ConversationAnalytics(business.ConversationAnalyticsOptions{ + // Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + // End: time.Now(), + // Granularity: business.ConversationAnalyticsGranularityTypeDay, + // }) + + client.Business.FetchAnalytics(business.AccountAnalyticsOptions{ + Start: time.Now().Add(-time.Hour * 24 * 7 * 30), + End: time.Now(), + Granularity: business.AnalyticsRequestGranularityTypeDay, + }) + + // client.Business.PhoneNumber.FetchAll(manager.FetchPhoneNumberFilters{ + // GetSandboxNumbers: false, + // }) + + // client.Business.PhoneNumber.Fetch("113269274970227") + // response, err := client.Business.Template.FetchAll() + + // response, err := client.Business.Template.Fetch(phoneNumber) - // create a message textMessage, err := wapiComponents.NewTextMessage(wapiComponents.TextMessageConfigs{ - Text: "Hello, from wapi.go", + Text: "Hello, how can I help you?", }) if err != nil { - fmt.Println("error creating text message", err) + fmt.Println("error creating text message message", err) return } @@ -114,9 +136,6 @@ func main() { } fmt.Println(string(jsonData)) - - whatsappClient.Message.Send(listMessage, "919643500545") - buttonMessage, err := wapiComponents.NewQuickReplyButtonMessage("Body 1") if err != nil { @@ -127,11 +146,11 @@ func main() { buttonMessage.AddButton("1", "Button 1") buttonMessage.AddButton("2", "Button 2") - whatsappClient.On(events.ReadyEventType, func(event events.BaseEvent) { + client.On(events.ReadyEventType, func(event events.BaseEvent) { fmt.Println("client is ready") }) - whatsappClient.On(events.TextMessageEventType, func(event events.BaseEvent) { + client.On(events.TextMessageEventType, func(event events.BaseEvent) { fmt.Println("text message event received") textMessageEvent := event.(*events.TextMessageEvent) @@ -155,26 +174,49 @@ func main() { textMessageEvent.Reply(listMessage) case "button": textMessageEvent.Reply(buttonMessage) + case "qr": + { + response, err := client.Business.PhoneNumber.GenerateQrCode(phoneNumber, "This is Wapi.go this side.") + + if err != nil { + fmt.Println("error generating qr code", err) + return + } + + fmt.Println("qr code response", response.QrImageUrl) + + qrCodeMessage, err := wapiComponents.NewImageMessage(wapiComponents.ImageMessageConfigs{ + Link: response.QrImageUrl, + // Caption: "Scan the QR code to start a conversation.", + }) + + if err != nil { + fmt.Println("error creating image message", err) + return + } + + textMessageEvent.Reply(qrCodeMessage) + } default: textMessageEvent.Reply(textMessage) } }) - whatsappClient.On(events.AudioMessageEventType, func(be events.BaseEvent) { + client.On(events.AudioMessageEventType, func(be events.BaseEvent) { fmt.Println("audio message event received") }) - whatsappClient.On(events.VideoMessageEventType, func(be events.BaseEvent) { + client.On(events.VideoMessageEventType, func(be events.BaseEvent) { fmt.Println("video message event received") }) - whatsappClient.On(events.DocumentMessageEventType, func(be events.BaseEvent) { + client.On(events.DocumentMessageEventType, func(be events.BaseEvent) { fmt.Println("document message event received") }) - whatsappClient.On(events.ImageMessageEventType, func(be events.BaseEvent) { + client.On(events.ImageMessageEventType, func(be events.BaseEvent) { fmt.Println("image message event received") }) - whatsappClient.InitiateClient() + client.Initiate() } diff --git a/examples/http-backend-integration/main.go b/examples/http-backend-integration/main.go index 40186c6..4e8fd25 100644 --- a/examples/http-backend-integration/main.go +++ b/examples/http-backend-integration/main.go @@ -11,8 +11,7 @@ import ( func main() { - whatsappClient, err := wapi.New(wapi.ClientConfig{ - PhoneNumberId: "", + client := wapi.New(&wapi.ClientConfig{ ApiAccessToken: "", BusinessAccountId: "", WebhookPath: "/webhook", @@ -20,7 +19,7 @@ func main() { WebhookServerPort: 8080, }) - whatsappClient.On(events.TextMessageEventType, func(event events.BaseEvent) { + client.On(events.TextMessageEventType, func(event events.BaseEvent) { textMessageEvent := event.(*events.TextMessageEvent) reply, err := components.NewTextMessage(components.TextMessageConfigs{ Text: "Hello, from wapi.go", @@ -32,13 +31,8 @@ func main() { textMessageEvent.Reply(reply) }) - getHandler := whatsappClient.GetWebhookGetRequestHandler() - postHandler := whatsappClient.GetWebhookPostRequestHandler() - - if err != nil { - fmt.Println("error creating client", err) - return - } + getHandler := client.GetWebhookGetRequestHandler() + postHandler := client.GetWebhookPostRequestHandler() server := echo.New() diff --git a/internal/manager/event_manager.go b/internal/manager/event_manager.go index 4faa733..e8392e1 100644 --- a/internal/manager/event_manager.go +++ b/internal/manager/event_manager.go @@ -13,22 +13,22 @@ type ChannelEvent struct { Data events.BaseEvent // Data is the data associated with the event. } -// EventManger is responsible for managing events and their subscribers. -type EventManger struct { +// EventManager is responsible for managing events and their subscribers. +type EventManager struct { subscribers map[events.EventType]chan ChannelEvent // subscribers is a map of event types to channels of ChannelEvent. sync.RWMutex // RWMutex is used to synchronize access to the subscribers map. } // NewEventManager creates a new instance of EventManger. -func NewEventManager() *EventManger { - return &EventManger{ +func NewEventManager() *EventManager { + return &EventManager{ subscribers: make(map[events.EventType]chan ChannelEvent), } } // Subscribe adds a new subscriber to the specified event type. // The subscriber will be notified when the event is published. -func (em *EventManger) Subscribe(eventName events.EventType) (chan ChannelEvent, error) { +func (em *EventManager) Subscribe(eventName events.EventType) (chan ChannelEvent, error) { em.Lock() defer em.Unlock() if ch, ok := em.subscribers[eventName]; ok { @@ -39,14 +39,14 @@ func (em *EventManger) Subscribe(eventName events.EventType) (chan ChannelEvent, } // Unsubscribe removes a subscriber from the specified event type. -func (em *EventManger) Unsubscribe(id events.EventType) { +func (em *EventManager) Unsubscribe(id events.EventType) { em.Lock() defer em.Unlock() delete(em.subscribers, id) } // Publish publishes an event to the event system and notifies all the subscribers. -func (em *EventManger) Publish(event events.EventType, data events.BaseEvent) error { +func (em *EventManager) Publish(event events.EventType, data events.BaseEvent) error { em.Lock() defer em.Unlock() @@ -66,7 +66,7 @@ func (em *EventManger) Publish(event events.EventType, data events.BaseEvent) er // On registers a handler function for the specified event type. // The handler function will be called whenever the event is published. // It returns the event type that the handler is registered for. -func (em *EventManger) On(eventName events.EventType, handler func(events.BaseEvent)) events.EventType { +func (em *EventManager) On(eventName events.EventType, handler func(events.BaseEvent)) events.EventType { ch, _ := em.Subscribe(eventName) go func() { for { diff --git a/internal/manager/media_manager.go b/internal/manager/media_manager.go index 556eb66..126ab5b 100644 --- a/internal/manager/media_manager.go +++ b/internal/manager/media_manager.go @@ -1,25 +1,30 @@ package manager -import requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" +import ( + "strings" + + "github.com/sarthakjdev/wapi.go/internal/request_client" +) // MediaManager is responsible for managing media related operations. type MediaManager struct { - requester requestclient.RequestClient + requester request_client.RequestClient } // NewMediaManager creates a new instance of MediaManager. -func NewMediaManager(requester requestclient.RequestClient) *MediaManager { +func NewMediaManager(requester request_client.RequestClient) *MediaManager { return &MediaManager{ requester: requester, } } // GetMediaUrlById retrieves the media URL by its ID. -func (mm *MediaManager) GetMediaUrlById() { +func (mm *MediaManager) GetMediaUrlById(id string) { + apiRequest := mm.requester.NewApiRequest(strings.Join([]string{"media", id}, "/"), "GET") + apiRequest.Execute() } // GetMediaIdByUrl retrieves the media ID by its URL. -func (mm *MediaManager) GetMediaIdByUrl() { - +func (mm *MediaManager) GetMediaIdByUrl(id string) { } diff --git a/internal/manager/message_manager.go b/internal/manager/message_manager.go index 508561a..91638b7 100644 --- a/internal/manager/message_manager.go +++ b/internal/manager/message_manager.go @@ -2,20 +2,24 @@ package manager import ( "fmt" + "net/http" + "strings" - requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/internal/request_client" "github.com/sarthakjdev/wapi.go/pkg/components" ) // MessageManager is responsible for managing messages. type MessageManager struct { - requester requestclient.RequestClient + requester request_client.RequestClient + PhoneNumberId string } // NewMessageManager creates a new instance of MessageManager. -func NewMessageManager(requester requestclient.RequestClient) *MessageManager { +func NewMessageManager(requester request_client.RequestClient, phoneNumberId string) *MessageManager { return &MessageManager{ - requester: requester, + requester: requester, + PhoneNumberId: phoneNumberId, } } @@ -30,9 +34,9 @@ func (mm *MessageManager) Send(message components.BaseMessage, phoneNumber strin // TODO: emit an error event here return "", fmt.Errorf("error converting message to json: %v", err) } - mm.requester.RequestCloudApi(requestclient.RequestCloudApiParams{ - Body: string(body), - Path: "/" + mm.requester.PhoneNumberId + "/messages", - }) + + apiRequest := mm.requester.NewApiRequest(strings.Join([]string{mm.PhoneNumberId, "messages"}, "/"), http.MethodPost) + apiRequest.SetBody(string(body)) + apiRequest.Execute() return "ok", nil } diff --git a/internal/manager/phone_number_manager.go b/internal/manager/phone_number_manager.go new file mode 100644 index 0000000..9f831e3 --- /dev/null +++ b/internal/manager/phone_number_manager.go @@ -0,0 +1,237 @@ +package manager + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/sarthakjdev/wapi.go/internal" + "github.com/sarthakjdev/wapi.go/internal/request_client" +) + +// PhoneNumberManager is responsible for managing phone numbers for WhatsApp Business API and phone number specific operations. +type PhoneNumberManager struct { + businessAccountId string + apiAccessToken string + requester *request_client.RequestClient +} + +// PhoneNumberManagerConfig holds the configuration for PhoneNumberManager. +type PhoneNumberManagerConfig struct { + BusinessAccountId string + ApiAccessToken string + Requester *request_client.RequestClient +} + +// NewPhoneNumberManager creates a new instance of PhoneNumberManager. +func NewPhoneNumberManager(config *PhoneNumberManagerConfig) *PhoneNumberManager { + return &PhoneNumberManager{ + apiAccessToken: config.ApiAccessToken, + businessAccountId: config.BusinessAccountId, + requester: config.Requester, + } +} + +// WhatsappBusinessAccountPhoneNumber represents a WhatsApp Business Account phone number. +type WhatsappBusinessAccountPhoneNumber struct { + VerifiedName string `json:"verified_name,omitempty"` + DisplayPhoneNumber string `json:"display_phone_number,omitempty"` + Id string `json:"id,omitempty"` + QualityRating string `json:"quality_rating,omitempty"` + CodeVerification struct { + Status string `json:"code_verification_status,omitempty"` + } `json:"code_verification_status,omitempty"` + PlatformType string `json:"platform_type,omitempty"` +} + +// WhatsappBusinessAccountPhoneNumberEdge represents a list of WhatsApp Business Account phone numbers. +type WhatsappBusinessAccountPhoneNumberEdge struct { + Data []WhatsappBusinessAccountPhoneNumber `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` + Summary string `json:"summary,omitempty"` +} + +// FetchPhoneNumberFilters holds the filters for fetching phone numbers. +type FetchPhoneNumberFilters struct { + GetSandboxNumbers bool +} + +// FetchAll fetches all phone numbers based on the provided filters. +func (manager *PhoneNumberManager) FetchAll(options FetchPhoneNumberFilters) (*WhatsappBusinessAccountPhoneNumberEdge, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "/", "phone_numbers"}, ""), http.MethodGet) + + apiRequest.AddQueryParam("filtering", `[{"field":"account_mode","operator":"EQUAL","value":"LIVE"}]`) + response, err := apiRequest.Execute() + + if err != nil { + return nil, err + } + + var responseToReturn WhatsappBusinessAccountPhoneNumberEdge + json.Unmarshal([]byte(response), &responseToReturn) + + return &responseToReturn, nil +} + +// Fetch fetches a phone number by its ID. +func (manager *PhoneNumberManager) Fetch(phoneNumberId string) (*WhatsappBusinessAccountPhoneNumber, error) { + apiRequest := manager.requester.NewApiRequest(phoneNumberId, http.MethodGet) + + response, err := apiRequest.Execute() + + if err != nil { + return nil, err + } + + var responseToReturn WhatsappBusinessAccountPhoneNumber + json.Unmarshal([]byte(response), &responseToReturn) + + return &responseToReturn, nil + +} + +type CreatePhoneNumberResponse struct { + Id string `json:"id,omitempty"` +} + +func (manager *PhoneNumberManager) Create(phoneNumber, verifiedName, countryCode string) (CreatePhoneNumberResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "/phone_numbers"}, ""), http.MethodPost) + apiRequest.AddQueryParam("phone_number", phoneNumber) + apiRequest.AddQueryParam("cc", countryCode) + apiRequest.AddQueryParam("verified_name", verifiedName) + response, err := apiRequest.Execute() + responseToReturn := CreatePhoneNumberResponse{} + json.Unmarshal([]byte(response), &responseToReturn) + return responseToReturn, err +} + +type VerifyCodeMethod string + +const ( + VerifyCodeMethodSms VerifyCodeMethod = "SMS" + VerifyCodeMethodVoice VerifyCodeMethod = "VOICE" +) + +type RequestVerificationCodeResponse struct { + Success bool `json:"success,omitempty"` +} + +func (manager *PhoneNumberManager) RequestVerificationCode(phoneNumberId string, codeMethod VerifyCodeMethod, languageCode string) (RequestVerificationCodeResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumberId, "request_code"}, "/"), http.MethodPost) + apiRequest.AddQueryParam("code_method", string(codeMethod)) + apiRequest.AddQueryParam("language", languageCode) + response, err := apiRequest.Execute() + responseToReturn := RequestVerificationCodeResponse{} + json.Unmarshal([]byte(response), &responseToReturn) + return responseToReturn, err +} + +type VerifyCodeResponse struct { + Success bool `json:"success,omitempty"` +} + +func (manager *PhoneNumberManager) VerifyCode(phoneNumberId, verificationCode string) (VerifyCodeResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumberId, "verify_code"}, "/"), http.MethodPost) + apiRequest.AddQueryParam("code", verificationCode) + response, err := apiRequest.Execute() + responseToReturn := VerifyCodeResponse{} + json.Unmarshal([]byte(response), &responseToReturn) + return responseToReturn, err +} + +// GenerateQrCodeResponse represents the response of generating a QR code. +type GenerateQrCodeResponse struct { + Code string `json:"code,omitempty"` + PrefilledMessage string `json:"prefilled_message,omitempty"` + DeepLinkUrl string `json:"deep_link_url,omitempty"` + QrImageUrl string `json:"qr_image_url,omitempty"` +} + +// GenerateQrCode generates a QR code for the specified phone number with the given prefilled message. +func (manager *PhoneNumberManager) GenerateQrCode(phoneNumber string, prefilledMessage string) (*GenerateQrCodeResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumber, "/message_qrdls"}, ""), http.MethodPost) + jsonBody, err := json.Marshal(map[string]string{ + "prefilled_message": prefilledMessage, + "generate_qr_image": "PNG", + }) + if err != nil { + return nil, err + } + apiRequest.SetBody(string(jsonBody)) + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + var responseToReturn GenerateQrCodeResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} + +// GetAllQrCodesResponse represents the response of getting all QR codes for a phone number. +type GetAllQrCodesResponse struct { + Data []GenerateQrCodeResponse `json:"data,omitempty"` +} + +// GetAllQrCodes gets all QR codes for the specified phone number. +func (manager *PhoneNumberManager) GetAllQrCodes(phoneNumber string) (*GetAllQrCodesResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumber, "/message_qrdls"}, ""), http.MethodGet) + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + + var responseToReturn GetAllQrCodesResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} + +// GetQrCodeById gets a QR code by its ID for the specified phone number. +func (manager *PhoneNumberManager) GetQrCodeById(phoneNumber, id string) (*GetAllQrCodesResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumber, "/message_qrdls", "/", id}, ""), http.MethodDelete) + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + var responseToReturn GetAllQrCodesResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} + +// DeleteQrCodeResponse represents the response of deleting a QR code. +type DeleteQrCodeResponse struct { + Success bool `json:"success,omitempty"` +} + +// DeleteQrCode deletes a QR code by its ID for the specified phone number. +func (manager *PhoneNumberManager) DeleteQrCode(phoneNumber, id string) (*DeleteQrCodeResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumber, "/message_qrdls", "/", id}, ""), http.MethodDelete) + response, err := apiRequest.Execute() + + if err != nil { + return nil, err + } + var responseToReturn DeleteQrCodeResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} + +// UpdateQrCode updates a QR code by its ID for the specified phone number with the given prefilled message. +func (manager *PhoneNumberManager) UpdateQrCode(phoneNumber, id, prefilledMessage string) (*GenerateQrCodeResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{phoneNumber, "/message_qrdls"}, ""), http.MethodPost) + jsonBody, err := json.Marshal(map[string]string{ + "prefilled_message": prefilledMessage, + "code": id, + }) + if err != nil { + return nil, err + } + apiRequest.SetBody(string(jsonBody)) + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + + var responseToReturn GenerateQrCodeResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} diff --git a/internal/manager/template_manager.go b/internal/manager/template_manager.go new file mode 100644 index 0000000..4edd592 --- /dev/null +++ b/internal/manager/template_manager.go @@ -0,0 +1,352 @@ +package manager + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/sarthakjdev/wapi.go/internal" + "github.com/sarthakjdev/wapi.go/internal/request_client" +) + +// MessageTemplateStatus represents the status of a WhatsApp Business message template. +type MessageTemplateStatus string + +// Constants representing different message template statuses. +const ( + MessageTemplateStatusApproved MessageTemplateStatus = "APPROVED" + MessageTemplateStatusRejected MessageTemplateStatus = "REJECTED" + MessageTemplateStatusPending MessageTemplateStatus = "PENDING" +) + +// MessageTemplateCategory represents the category of a WhatsApp Business message template. +type MessageTemplateCategory string + +// Constants representing different message template categories. +const ( + MessageTemplateCategoryUtility MessageTemplateCategory = "UTILITY" + MessageTemplateCategoryMarketing MessageTemplateCategory = "MARKETING" + MessageTemplateCategoryAuthentication MessageTemplateCategory = "AUTHENTICATION" +) + +// WhatsAppBusinessMessageTemplateNode represents a WhatsApp Business message template. +type WhatsAppBusinessMessageTemplateNode struct { + Id string `json:"id,omitempty"` + Category MessageTemplateCategory `json:"category,omitempty"` + Components []WhatsAppBusinessHSMWhatsAppHSMComponent `json:"components,omitempty"` + CorrectCategory string `json:"correct_category,omitempty"` + CtaUrlLinkTrackingOptedOut bool `json:"cta_url_link_tracking_opted_out,omitempty"` + Language string `json:"language,omitempty"` + LibraryTemplateName string `json:"library_template_name,omitempty"` + MessageSendTtlSeconds int `json:"message_send_ttl_seconds,omitempty"` + Name string `json:"name,omitempty"` + PreviousCategory string `json:"previous_category,omitempty"` + QualityScore WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape `json:"quality_score,omitempty"` + RejectedReason string `json:"rejected_reason,omitempty"` + Status MessageTemplateStatus `json:"status,omitempty"` +} + +// TemplateManager is responsible for managing WhatsApp Business message templates. +type TemplateManager struct { + businessAccountId string + apiAccessToken string + requester *request_client.RequestClient +} + +// TemplateManagerConfig represents the configuration for creating a new TemplateManager. +type TemplateManagerConfig struct { + BusinessAccountId string + ApiAccessToken string + Requester *request_client.RequestClient +} + +// NewTemplateManager creates a new TemplateManager with the given configuration. +func NewTemplateManager(config *TemplateManagerConfig) *TemplateManager { + return &TemplateManager{ + apiAccessToken: config.ApiAccessToken, + businessAccountId: config.BusinessAccountId, + requester: config.Requester, + } +} + +// WhatsAppBusinessTemplatesFetchResponseEdge represents the response structure for fetching WhatsApp Business message templates. +type WhatsAppBusinessTemplatesFetchResponseEdge struct { + Data []WhatsAppBusinessMessageTemplateNode `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` +} + +// WhatsAppBusinessHSMWhatsAppHSMComponentCard represents a card component in a WhatsApp Business message template. +type WhatsAppBusinessHSMWhatsAppHSMComponentCard struct { +} + +// WhatsAppBusinessHSMWhatsAppHSMComponentButton represents a button component in a WhatsApp Business message template. +type WhatsAppBusinessHSMWhatsAppHSMComponentButton struct { +} + +// WhatsAppBusinessHSMWhatsAppHSMComponentExample represents an example component in a WhatsApp Business message template. +type WhatsAppBusinessHSMWhatsAppHSMComponentExample struct { +} + +// WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape represents a limited time offer parameter in a WhatsApp Business message template. +type WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape struct { +} + +// WhatsAppBusinessHSMWhatsAppHSMComponent represents a component in a WhatsApp Business message template. +type WhatsAppBusinessHSMWhatsAppHSMComponent struct { + AddSecurityRecommendation bool `json:"add_security_recommendation,omitempty"` + Buttons []WhatsAppBusinessHSMWhatsAppHSMComponentButton `json:"buttons,omitempty"` + Cards []WhatsAppBusinessHSMWhatsAppHSMComponentCard `json:"cards,omitempty"` + CodeExpirationMinutes int `json:"code_expiration_minutes,omitempty"` + Example WhatsAppBusinessHSMWhatsAppHSMComponentExample `json:"example,omitempty"` + Format string `json:"format,omitempty"` + LimitedTimeOffer WhatsAppBusinessHSMWhatsAppLimitedTimeOfferParameterShape `json:"limited_time_offer,omitempty"` + Text string `json:"text,omitempty"` + Type string `json:"type,omitempty"` +} + +// WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape represents the quality score of a WhatsApp Business message template. +type WhatsAppBusinessHSMWhatsAppBusinessHSMQualityScoreShape struct { + Date int `json:"date,omitempty"` + Reasons []string `json:"reasons,omitempty"` + Score int `json:"score,omitempty"` +} + +// FetchAll fetches all WhatsApp Business message templates. +func (manager *TemplateManager) FetchAll() (*WhatsAppBusinessTemplatesFetchResponseEdge, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "/", "message_templates"}, ""), http.MethodGet) + + fields := []string{"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out", "language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category", "quality_score", "rejected_reason", "status", "sub_category"} + + for _, field := range fields { + apiRequest.AddField(request_client.ApiRequestQueryParamField{ + Name: field, + Filters: map[string]string{}, + }) + } + + response, err := apiRequest.Execute() + + if err != nil { + return nil, err + } + + var responseToReturn WhatsAppBusinessTemplatesFetchResponseEdge + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} + +// Fetch fetches a single WhatsApp Business message template by its ID. +func (manager *TemplateManager) Fetch(Id string) (*WhatsAppBusinessMessageTemplateNode, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{Id}, ""), http.MethodGet) + fields := []string{"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out", "language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category", "quality_score", "rejected_reason", "status", "sub_category"} + for _, field := range fields { + apiRequest.AddField(request_client.ApiRequestQueryParamField{ + Name: field, + Filters: map[string]string{}, + }) + } + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + var responseToReturn WhatsAppBusinessMessageTemplateNode + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil +} + +// WhatsappMessageTemplateButtonCreateRequestBody represents the request body for creating a button in a message template. +type WhatsappMessageTemplateButtonCreateRequestBody struct { + // enum {QUICK_REPLY, URL, PHONE_NUMBER, OTP, MPM, CATALOG, FLOW, VOICE_CALL} + Type string `json:"type,omitempty"` + Text string `json:"text,omitempty"` + Url string `json:"url,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + Example string `json:"example,omitempty"` + FlowId string `json:"flow_id,omitempty"` + ZeroTapTermsAccepted bool `json:"zero_tap_terms_accepted,omitempty"` +} + +// MessageTemplateComponentType represents the type of a message template component. +type MessageTemplateComponentType string + +// Constants representing different message template component types. +const ( + MessageTemplateComponentTypeGreeting MessageTemplateComponentType = "GREETING" + MessageTemplateComponentTypeHeader MessageTemplateComponentType = "HEADER" + MessageTemplateComponentTypeBody MessageTemplateComponentType = "BODY" + MessageTemplateComponentTypeFooter MessageTemplateComponentType = "FOOTER" + MessageTemplateComponentTypeButtons MessageTemplateComponentType = "BUTTONS" + MessageTemplateComponentTypeCarousel MessageTemplateComponentType = "CAROUSEL" + MessageTemplateComponentTypeLimitedTimeOffer MessageTemplateComponentType = "LIMITED_TIME_OFFER" +) + +// MessageTemplateComponentFormat represents the format of a message template component. +type MessageTemplateComponentFormat string + +// Constants representing different message template component formats. +const ( + MessageTemplateComponentFormatText MessageTemplateComponentFormat = "TEXT" + MessageTemplateComponentFormatImage MessageTemplateComponentFormat = "IMAGE" + MessageTemplateComponentFormatDocument MessageTemplateComponentFormat = "DOCUMENT" + MessageTemplateComponentFormatVideo MessageTemplateComponentFormat = "VIDEO" + MessageTemplateComponentFormatLocation MessageTemplateComponentFormat = "LOCATION" +) + +// WhatsappMessageTemplateComponentCreateOrUpdateRequestBody represents the request body for creating or updating a component in a message template. +type WhatsappMessageTemplateComponentCreateOrUpdateRequestBody struct { + Type MessageTemplateComponentType `json:"type,omitempty"` + Format MessageTemplateComponentFormat `json:"format,omitempty"` + Text string `json:"text,omitempty"` + Buttons []WhatsappMessageTemplateButtonCreateRequestBody `json:"buttons,omitempty"` +} + +// AddButton adds a button to the component. +func (component *WhatsappMessageTemplateComponentCreateOrUpdateRequestBody) AddButton(button WhatsappMessageTemplateButtonCreateRequestBody) { + component.Buttons = append(component.Buttons, button) +} + +// WhatsappMessageTemplateCreateRequestBody represents the request body for creating a message template. +type WhatsappMessageTemplateCreateRequestBody struct { + AllowCategoryChange bool `json:"allow_category_change,omitempty" ` + + // enum {UTILITY, MARKETING, AUTHENTICATION} + Category string `json:"category,omitempty" validate:"required"` + + Components []WhatsappMessageTemplateComponentCreateOrUpdateRequestBody `json:"components" validate:"required"` + Name string `json:"name,omitempty" validate:"required"` + Language string `json:"language" validate:"required"` + LibraryTemplateName string `json:"library_template_name,omitempty"` + LibraryTemplateButtonInputs []WhatsappMessageTemplateButtonCreateRequestBody `json:"library_template_button_inputs,omitempty"` +} + +func (body *WhatsappMessageTemplateCreateRequestBody) AddComponent(component WhatsappMessageTemplateComponentCreateOrUpdateRequestBody) { + body.Components = append(body.Components, component) +} + +type MessageTemplateCreationResponse struct { + Id string `json:"id,omitempty"` + Status MessageTemplateStatus `json:"status,omitempty"` + Category MessageTemplateCategory `json:"category,omitempty"` +} + +func (manager *TemplateManager) Create(body WhatsappMessageTemplateCreateRequestBody) (*MessageTemplateCreationResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "/", "message_templates"}, ""), http.MethodPost) + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, err + } + apiRequest.SetBody(string(jsonBody)) + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + + var responseToReturn MessageTemplateCreationResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil + +} + +// this is the request body for the message template update request +type WhatsAppBusinessAccountMessageTemplateUpdateRequestBody struct { + Components []WhatsappMessageTemplateComponentCreateOrUpdateRequestBody `json:"components,omitempty"` + Category string `json:"category,omitempty"` + MessageSendTtlSeconds int `json:"message_send_ttl_seconds,omitempty"` +} + +func (manager *TemplateManager) Update(templateId string, updates WhatsAppBusinessAccountMessageTemplateUpdateRequestBody) (*MessageTemplateCreationResponse, error) { + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{templateId}, ""), http.MethodPost) + jsonBody, err := json.Marshal(updates) + if err != nil { + return nil, err + } + apiRequest.SetBody(string(jsonBody)) + response, err := apiRequest.Execute() + if err != nil { + return nil, err + } + + var responseToReturn MessageTemplateCreationResponse + json.Unmarshal([]byte(response), &responseToReturn) + return &responseToReturn, nil + + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-hsm/#:~:text=2.0%20Access%20Token-,Updating,-You%20can%20update +} + +type WhatsAppBusinessAccountMessageTemplateDeleteRequestBody struct { + HsmId string `json:"hsm_id,omitempty"` + Name string `json:"name,omitempty"` +} + +func (tm *TemplateManager) Delete(id string) { + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/message_templates/#:~:text=on%20this%20endpoint.-,Deleting,-You%20can%20dissociate +} + +type WhatsAppBusinessAccountMessageTemplatePreviewButton struct { + AutoFillText string `json:"auto_fill_text,omitempty"` + Text string `json:"text,omitempty"` +} + +type TemplateMessagePreviewNode struct { + Body string `json:"body,omitempty"` + Buttons []WhatsAppBusinessAccountMessageTemplatePreviewButton `json:"buttons,omitempty"` + Footer string `json:"footer,omitempty"` + Header string `json:"header,omitempty"` + Language string `json:"language,omitempty"` +} + +type TemplateMessagePreviewEdge struct { + Data []TemplateMessagePreviewNode `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` +} + +func (tm *TemplateManager) FetchMessageTemplatePreviews() { + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/message_template_previews/ +} + +type TemplateAnalyticsType struct { +} + +type TemplatePerformanceAnalytics struct { +} + +func (manager *TemplateManager) FetchPerformanceAnalytics(templateName, templateId string) (string, error) { + // /v20.0/{whats-app-business-account-id}/template_performance_metrics + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/template_performance_metrics/ + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "template_performance_metrics"}, "/"), http.MethodGet) + apiRequest.AddQueryParam("name", templateName) + apiRequest.AddQueryParam("template_id", templateId) + response, err := apiRequest.Execute() + + if err != nil { + return "", err + } + + return response, nil +} + +func (manager *TemplateManager) MigrateFromOtherBusinessAccount(sourcePageNumber int, sourceWabaId int) (string, error) { + // /{whats_app_business_account_id}/migrate_message_templates + + apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "migrate_message_templates"}, "/"), http.MethodGet) + apiRequest.AddQueryParam("page_number", string(sourcePageNumber)) + apiRequest.AddQueryParam("source_waba_id", string(sourceWabaId)) + response, err := apiRequest.Execute() + + if err != nil { + return "", err + } + + return response, nil + + // Struct { + // migrated_templates: List [ + // string + // ], + // failed_templates: Map { + // string: string + // }, + // } + + // return type +} diff --git a/internal/manager/types.go b/internal/manager/types.go index b4c4917..000bc17 100644 --- a/internal/manager/types.go +++ b/internal/manager/types.go @@ -134,10 +134,27 @@ type NotificationPayloadReactionMessageSchemaType struct { type NotificationPayloadInteractionMessageSchemaType struct { Interactive struct { - Type InteractiveNotificationTypeEnum `json:"type"` + Type InteractiveNotificationTypeEnum `json:"type"` + NotificationPayloadButtonInteractionMessageSchemaType `json:",inline,omitempty"` + NotificationPayloadListInteractionMessageSchemaType `json:",inline,omitempty"` } `json:"interactive,omitempty"` } +type NotificationPayloadButtonInteractionMessageSchemaType struct { + ButtonReply struct { + ReplyId string `json:"reply_id"` + Title string `json:"title"` + } `json:"button_reply,omitempty"` +} + +type NotificationPayloadListInteractionMessageSchemaType struct { + ListReply struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + } `json:"list_reply,omitempty"` +} + type NotificationPayloadLocationMessageSchemaType struct { Location struct { Latitude float64 `json:"latitude"` @@ -195,8 +212,8 @@ const ( type SystemNotificationTypeEnum string const ( - SystemNotificationTypeUnknown SystemNotificationTypeEnum = "unknown" - // Add other system notification types + SystemNotificationTypeCustomerPhoneNumberChange SystemNotificationTypeEnum = "user_changed_number" + SystemNotificationTypeCustomerIdentityChanged SystemNotificationTypeEnum = "customer_identity_changed" ) type Contact struct { @@ -262,24 +279,25 @@ type Pricing struct { } type Message struct { - Id string `json:"id"` - From string `json:"from"` - Timestamp string `json:"timestamp"` - Type NotificationMessageTypeEnum `json:"type"` - Context NotificationPayloadMessageContextSchemaType `json:"context"` - Errors []Error `json:",inline"` - NotificationPayloadTextMessageSchemaType `json:",inline"` - NotificationPayloadAudioMessageSchemaType `json:",inline"` - NotificationPayloadImageMessageSchemaType `json:",inline"` - NotificationPayloadButtonMessageSchemaType `json:",inline"` - NotificationPayloadDocumentMessageSchemaType `json:",inline"` - NotificationPayloadOrderMessageSchemaType `json:",inline"` - NotificationPayloadStickerMessageSchemaType `json:",inline"` - NotificationPayloadSystemMessageSchemaType `json:",inline"` - NotificationPayloadVideoMessageSchemaType `json:",inline"` - NotificationPayloadReactionMessageSchemaType `json:",inline"` - NotificationPayloadLocationMessageSchemaType `json:",inline"` - NotificationPayloadContactMessageSchemaType `json:",inline"` + Id string `json:"id"` + From string `json:"from"` + Timestamp string `json:"timestamp"` + Type NotificationMessageTypeEnum `json:"type"` + Context NotificationPayloadMessageContextSchemaType `json:"context"` + Errors []Error `json:",inline"` + NotificationPayloadTextMessageSchemaType `json:",inline"` + NotificationPayloadAudioMessageSchemaType `json:",inline"` + NotificationPayloadImageMessageSchemaType `json:",inline"` + NotificationPayloadButtonMessageSchemaType `json:",inline"` + NotificationPayloadDocumentMessageSchemaType `json:",inline"` + NotificationPayloadOrderMessageSchemaType `json:",inline"` + NotificationPayloadStickerMessageSchemaType `json:",inline"` + NotificationPayloadSystemMessageSchemaType `json:",inline"` + NotificationPayloadVideoMessageSchemaType `json:",inline"` + NotificationPayloadReactionMessageSchemaType `json:",inline"` + NotificationPayloadLocationMessageSchemaType `json:",inline"` + NotificationPayloadContactMessageSchemaType `json:",inline"` + NotificationPayloadInteractionMessageSchemaType `json:",inline"` } type Error struct { @@ -292,11 +310,12 @@ const ( MessageStatusCategorySent MessageStatusCategoryEnum = "sent" ) -// Add other message status categories - type MessageStatusEnum string const ( - MessageStatusDelivered MessageStatusEnum = "delivered" - // Add other message statuses + MessageStatusDelivered MessageStatusEnum = "delivered" + MessageStatusRead MessageStatusEnum = "read" + MessageStatusUnDelivered MessageStatusEnum = "undelivered" + MessageStatusFailed MessageStatusEnum = "failed" + MessageStatusSent MessageStatusEnum = "sent" ) diff --git a/internal/manager/webhook_manager.go b/internal/manager/webhook_manager.go index 5e6cd57..9ae8257 100644 --- a/internal/manager/webhook_manager.go +++ b/internal/manager/webhook_manager.go @@ -1,4 +1,3 @@ -// Package manager provides functionality for managing webhooks. package manager import ( @@ -13,7 +12,8 @@ import ( "github.com/labstack/echo/v4" "github.com/sarthakjdev/wapi.go/internal" - requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/pkg/components" "github.com/sarthakjdev/wapi.go/pkg/events" ) @@ -22,21 +22,25 @@ type WebhookManager struct { secret string path string port int - EventManager EventManger - Requester requestclient.RequestClient + EventManager EventManager + Requester request_client.RequestClient } // WebhookManagerConfig represents the configuration options for creating a new WebhookManager. type WebhookManagerConfig struct { - Secret string + Secret string `validate:"required"` + EventManager EventManager `validate:"required"` + Requester request_client.RequestClient `validate:"required"` Path string Port int - EventManager EventManger - Requester requestclient.RequestClient } // NewWebhook creates a new WebhookManager with the given options. func NewWebhook(options *WebhookManagerConfig) *WebhookManager { + if err := internal.GetValidator().Struct(options); err != nil { + fmt.Println("Error validating WebhookManagerConfig:", err) + return nil + } return &WebhookManager{ secret: options.Secret, path: options.Path, @@ -57,8 +61,9 @@ func (wh *WebhookManager) createEchoHttpServer() *echo.Echo { func (wh *WebhookManager) GetRequestHandler(c echo.Context) error { hubVerificationToken := c.QueryParam("hub.verify_token") hubChallenge := c.QueryParam("hub.challenge") + hubMode := c.QueryParam("hub.mode") fmt.Println(hubVerificationToken, hubChallenge) - if hubVerificationToken == wh.secret { + if hubMode == "subscribe" && hubVerificationToken == wh.secret { return c.String(200, hubChallenge) } else { return c.String(400, "invalid token") @@ -87,14 +92,51 @@ func (wh *WebhookManager) PostRequestHandler(c echo.Context) error { for _, entry := range payload.Entry { for _, change := range entry.Changes { + + if len(change.Value.Statuses) > 0 { + for _, status := range change.Value.Statuses { + + switch status.Status { + case string(MessageStatusDelivered): + { + wh.EventManager.Publish(events.MessageDeliveredEventType, events.NewMessageDeliveredEvent(events.BaseSystemEvent{ + Timestamp: status.Timestamp, + }, status.Conversation.Id, status.RecipientId)) + } + + case string(MessageStatusRead): + { + wh.EventManager.Publish(events.MessageReadEventType, events.NewMessageReadEvent(events.BaseSystemEvent{ + Timestamp: status.Timestamp, + }, status.Conversation.Id, status.RecipientId)) + } + case string(MessageStatusSent): + { + wh.EventManager.Publish(events.MessageSentEventType, events.NewMessageSentEvent(events.BaseSystemEvent{ + Timestamp: status.Timestamp, + }, status.Conversation.Id, status.RecipientId)) + } + case string(MessageStatusFailed): + { + // ! TODO: check and properly emit the error event here. + } + } + + } + } + + // this is the id of the phone number which can ve used to respond to the events received + phoneNumberId := change.Value.Metadata.PhoneNumberId + for _, message := range change.Value.Messages { switch message.Type { case NotificationMessageTypeText: { wh.EventManager.Publish(events.TextMessageEventType, events.NewTextMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), @@ -104,74 +146,140 @@ func (wh *WebhookManager) PostRequestHandler(c echo.Context) error { case NotificationMessageTypeImage: { - wh.EventManager.Publish(events.ImageMessageEventType, events.NewTextMessageEvent( + imageMessageComponent, err := components.NewImageMessage(components.ImageMessageConfigs{ + Id: message.Image.Id, + Caption: message.Image.Caption, + }) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating image message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.ImageMessageEventType, events.NewImageMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), + *imageMessageComponent, + message.Image.MIMEType, message.Image.SHA256, message.Image.Id), ) - } - case NotificationMessageTypeVideo: + case NotificationMessageTypeAudio: { - wh.EventManager.Publish(events.AudioMessageEventType, events.NewTextMessageEvent( + audioMessageComponent, err := components.NewAudioMessage(components.AudioMessageConfigs{ + Id: message.Audio.Id, + }) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating audio message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.AudioMessageEventType, events.NewAudioMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), + *audioMessageComponent, + message.Audio.MIMEType, message.Audio.SHA256, message.Audio.Id), ) } - case NotificationMessageTypeDocument: + case NotificationMessageTypeVideo: { - wh.EventManager.Publish(events.DocumentMessageEventType, events.NewTextMessageEvent( + videoMessageComponent, err := components.NewVideoMessage(components.VideoMessageConfigs{ + Id: message.Video.Id, + Caption: message.Video.Caption, + }) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating Video message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.VideoMessageEventType, events.NewVideoMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), + *videoMessageComponent, + message.Video.MIMEType, message.Video.SHA256, message.Video.Id), ) + } - case NotificationMessageTypeAudio: + case NotificationMessageTypeDocument: { - wh.EventManager.Publish(events.AudioMessageEventType, events.NewTextMessageEvent( + documentMessageComponent, err := components.NewVideoMessage(components.VideoMessageConfigs{ + Id: message.Document.Id, + Caption: message.Document.Caption, + }) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating document message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.DocumentMessageEventType, events.NewVideoMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), + *documentMessageComponent, + message.Document.MIMEType, message.Document.SHA256, message.Document.Id), ) } case NotificationMessageTypeLocation: { - wh.EventManager.Publish(events.LocationMessageEventType, events.NewTextMessageEvent( + locationMessageComponent, err := components.NewLocationMessage(message.Location.Latitude, message.Location.Longitude) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating location message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.LocationMessageEventType, events.NewLocationMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), + *locationMessageComponent), ) } case NotificationMessageTypeContacts: { wh.EventManager.Publish(events.ContactMessageEventType, events.NewTextMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), @@ -180,87 +288,137 @@ func (wh *WebhookManager) PostRequestHandler(c echo.Context) error { } case NotificationMessageTypeSticker: { - wh.EventManager.Publish(events.StickerMessageEventType, events.NewTextMessageEvent( - events.NewBaseMessageEvent( - message.Id, - "", - message.From, - message.Context.Forwarded, - wh.Requester), - message.Text.Body), - ) - } - case NotificationMessageTypeSystem: - { - wh.EventManager.Publish(events.TextMessageEventType, events.NewTextMessageEvent( + + stickerMessageComponent, err := components.NewStickerMessage(&components.StickerMessageConfigs{ + Id: message.Sticker.Id, + }) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating Sticker message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.StickerMessageEventType, events.NewStickerMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), + *stickerMessageComponent, + message.Sticker.MIMEType, message.Sticker.SHA256, message.Sticker.Id), ) + } case NotificationMessageTypeButton: { - wh.EventManager.Publish(events.ReplyButtonInteractionEventType, events.NewTextMessageEvent( + wh.EventManager.Publish(events.QuickReplyMessageEventType, events.NewQuickReplyButtonInteractionEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), - ) + message.Button.Text, + message.Button.Payload, + )) } case NotificationMessageTypeInteractive: { - wh.EventManager.Publish(events.ListInteractionMessageEventType, events.NewTextMessageEvent( - events.NewBaseMessageEvent( - message.Id, - "", - message.From, - message.Context.Forwarded, - wh.Requester), - message.Text.Body), - ) + if message.Interactive.Type == "list" { + wh.EventManager.Publish(events.ListInteractionMessageEventType, events.NewListInteractionEvent( + events.NewBaseMessageEvent( + phoneNumberId, + message.Id, + message.Timestamp, + message.From, + message.Context.Forwarded, + wh.Requester), + message.Interactive.ListReply.Title, + message.Interactive.ListReply.Id, + message.Interactive.ListReply.Description, + )) + } else { + wh.EventManager.Publish(events.ReplyButtonInteractionEventType, events.NewReplyButtonInteractionEvent( + events.NewBaseMessageEvent( + phoneNumberId, + message.Id, + message.Timestamp, + message.From, + message.Context.Forwarded, + wh.Requester), + message.Interactive.ButtonReply.Title, + message.Interactive.ButtonReply.ReplyId, + )) + } } case NotificationMessageTypeReaction: { - wh.EventManager.Publish(events.ReactionMessageEventType, events.NewTextMessageEvent( + reactionMessageComponent, err := components.NewReactionMessage(components.ReactionMessageParams{ + MessageId: message.Reaction.MessageId, + Emoji: message.Reaction.Emoji, + }) + + if err != nil { + // ! TODO: emit error event here + fmt.Println("Error creating location message:", err) + c.String(500, "Internal server error") + return err + } + + wh.EventManager.Publish(events.ReactionMessageEventType, events.NewReactionMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), - message.Text.Body), - ) + *reactionMessageComponent, + )) } case NotificationMessageTypeOrder: { wh.EventManager.Publish(events.OrderReceivedEventType, events.NewTextMessageEvent( events.NewBaseMessageEvent( + phoneNumberId, message.Id, - "", + message.Timestamp, message.From, message.Context.Forwarded, wh.Requester), message.Text.Body), ) } + case NotificationMessageTypeSystem: + { + if message.System.Type == SystemNotificationTypeCustomerIdentityChanged { + wh.EventManager.Publish(events.CustomerIdentityChangedEventType, events.CustomerIdentityChangedEvent{ + BaseSystemEvent: events.BaseSystemEvent{ + Timestamp: message.Timestamp, + }, + Acknowledged: message.Identity.Acknowledged, + CreationTimestamp: message.Identity.CreatedTimestamp, + Hash: message.Identity.Hash, + }) + } else { + wh.EventManager.Publish(events.CustomerNumberChangedEventType, events.CustomerNumberChangedEvent{ + BaseSystemEvent: events.BaseSystemEvent{ + Timestamp: message.Timestamp, + }, + NewWaId: message.System.WaId, + OldWaId: message.System.Customer, + ChangeDescription: message.System.Body, + }) + } + } case NotificationMessageTypeUnknown: { - wh.EventManager.Publish(events.UnknownEventType, events.NewTextMessageEvent( - events.NewBaseMessageEvent( - message.Id, - "", - message.From, - message.Context.Forwarded, - wh.Requester), - message.Text.Body), - ) + // ! TODO: handle error in the event and then emit it. } } @@ -269,9 +427,6 @@ func (wh *WebhookManager) PostRequestHandler(c echo.Context) error { } c.String(200, "Message received") - - fmt.Println("Received valid payload:", payload.Entry[0].Changes[0].Value.Messages[0].Type) - return nil } @@ -284,7 +439,7 @@ func (wh *WebhookManager) ListenToEvents() { // Start server in a goroutine go func() { - if err := server.Start(":8080"); err != nil { + if err := server.Start("127.0.0.1:8080"); err != nil { return } }() diff --git a/internal/request_client/request_client.go b/internal/request_client/request_client.go index a333205..ad45bf6 100644 --- a/internal/request_client/request_client.go +++ b/internal/request_client/request_client.go @@ -1,4 +1,4 @@ -package requestclient +package request_client import ( "fmt" @@ -8,46 +8,72 @@ import ( ) const ( - API_VERSION = "v19.0" + API_VERSION = "v20.0" BASE_URL = "graph.facebook.com" REQUEST_PROTOCOL = "https" ) +type WhatsappApiType string + +const ( + WhatsappApiTypeMessaging WhatsappApiType = "messaging" + WhatsappApiTypeBusiness WhatsappApiType = "business" +) + // RequestClient represents a client for making requests to a cloud API. type RequestClient struct { apiVersion string - PhoneNumberId string baseUrl string apiAccessToken string } // NewRequestClient creates a new instance of RequestClient. -func NewRequestClient(phoneNumberId string, apiAccessToken string) *RequestClient { +func NewRequestClient(apiAccessToken string) *RequestClient { return &RequestClient{ apiVersion: API_VERSION, baseUrl: BASE_URL, - PhoneNumberId: phoneNumberId, apiAccessToken: apiAccessToken, } } // RequestCloudApiParams represents the parameters for making a request to the cloud API. type RequestCloudApiParams struct { - Body string - Path string + Body string + Path string + Method string + QueryParam map[string]string } -// RequestCloudApi makes a request to the cloud API with the given parameters. -// It returns the response body as a string and any error encountered. -func (requestClientInstance *RequestClient) RequestCloudApi(params RequestCloudApiParams) (string, error) { - httpRequest, err := http.NewRequest("POST", fmt.Sprintf("%s://%s/%s", REQUEST_PROTOCOL, requestClientInstance.baseUrl, params.Path), strings.NewReader(params.Body)) +func (requestClientInstance *RequestClient) request(params RequestCloudApiParams) (string, error) { + queryParamString := "" + if len(params.QueryParam) > 0 { + queryParamString = "?" + for key, value := range params.QueryParam { + fmt.Println("Key is", key, "Value is", value) + fmt.Println("queryParamString is", queryParamString) + // if first query param, don't add "&" + if queryParamString != "?" { + queryParamString += "&" + queryParamString += strings.Join([]string{queryParamString, key, "=", value}, "") + } else { + queryParamString += strings.Join([]string{key, "=", value}, "") + } + } + } + + requestPath := strings.Join( + []string{REQUEST_PROTOCOL, "://", requestClientInstance.baseUrl, "/", requestClientInstance.apiVersion, "/", params.Path, queryParamString}, "") + + httpRequest, err := http.NewRequest(params.Method, + requestPath, + strings.NewReader(params.Body)) if err != nil { return "", err } httpRequest.Header.Set("Content-Type", "application/json") httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", requestClientInstance.apiAccessToken)) - client := &http.Client{} - response, err := client.Do(httpRequest) + httpClient := &http.Client{} + response, err := httpClient.Do(httpRequest) if err != nil { fmt.Println("Error while requesting cloud api", err) return "", err @@ -57,8 +83,100 @@ func (requestClientInstance *RequestClient) RequestCloudApi(params RequestCloudA if err != nil { return "", err } + return string(body), nil +} + +func (client *RequestClient) NewApiRequest(path, method string) *ApiRequest { + return &ApiRequest{ + Path: path, + Fields: []ApiRequestQueryParamField{}, + Requester: client, + Method: method, + QueryParams: map[string]string{}, + } +} - fmt.Println("Response from cloud api is", string(body)) +type ApiRequestQueryParamField struct { + Name string + Filters map[string]string +} - return string(body), nil +func (field *ApiRequestQueryParamField) AddFilter(key, value string) { + field.Filters[key] = value +} + +type ApiRequest struct { + Path string + Method string + Body string + Fields []ApiRequestQueryParamField + QueryParams map[string]string + Requester *RequestClient +} + +func (request *ApiRequest) AddField(field ApiRequestQueryParamField) *ApiRequestQueryParamField { + // * NOTE: when we say we need to add a field to the request, it means we need to add a query param to the request with key "fields" + // * note that if there need to be multiple fields in a single request then the list of fields should be command separated + // * for example: fields=field1,field2,field3 + // * also note that if there filters in a field then they should be called like a function in the param string, for ex: fields=field1.filter1(value1).filter2(value2),field2.filter1(value1) + request.Fields = append(request.Fields, field) + return &field +} + +// AddQueryParam adds a query parameter to the request. +func (request *ApiRequest) AddQueryParam(key, value string) { + request.QueryParams[key] = value +} + +// SetMethod sets the method for the request. +func (request *ApiRequest) SetMethod(method string) { + request.Method = method +} + +// SetBody sets the body for the request. +func (request *ApiRequest) SetBody(body string) { + request.Body = body +} + +// Execute executes the request and returns the response. +func (request *ApiRequest) Execute() (string, error) { + // check if there are any fields in the request + var queryParam = map[string]string{} + if len(request.Fields) > 0 { + fieldsString := "" + for _, field := range request.Fields { + newFieldString := "" + if fieldsString != "" { + newFieldString = "," + } + filterString := "" + for key, value := range field.Filters { + filterString += strings.Join([]string{".", key, "(", value, ")"}, "") + } + newFieldString += strings.Join([]string{field.Name, filterString}, "") + fieldsString += newFieldString + } + + queryParam["fields"] = fieldsString + } + + if len(request.QueryParams) > 0 { + for key, value := range request.QueryParams { + queryParam[key] = value + } + } + + response, err := request.Requester.request(RequestCloudApiParams{ + Path: request.Path, + Body: request.Body, + Method: request.Method, + QueryParam: queryParam, + }) + + if err != nil { + fmt.Println("Error while executing business api request", err) + return "", nil + } + + return response, err } diff --git a/internal/types.go b/internal/types.go new file mode 100644 index 0000000..fa55830 --- /dev/null +++ b/internal/types.go @@ -0,0 +1,10 @@ +package internal + +type WhatsAppBusinessApiPaginationMeta struct { + Paging struct { + Cursors struct { + Before string `json:"before,omitempty"` + After string `json:"after,omitempty"` + } `json:"cursors,omitempty"` + } `json:"paging,omitempty"` +} diff --git a/pkg/business/client.go b/pkg/business/client.go new file mode 100644 index 0000000..d6628b1 --- /dev/null +++ b/pkg/business/client.go @@ -0,0 +1,371 @@ +package business + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/sarthakjdev/wapi.go/internal" + "github.com/sarthakjdev/wapi.go/internal/manager" + "github.com/sarthakjdev/wapi.go/internal/request_client" +) + +type BusinessClient struct { + BusinessAccountId string `json:"businessAccountId" validate:"required"` + AccessToken string `json:"accessToken" validate:"required"` + PhoneNumber *manager.PhoneNumberManager + Template *manager.TemplateManager + requester *request_client.RequestClient +} + +type BusinessClientConfig struct { + BusinessAccountId string `json:"businessAccountId" validate:"required"` + AccessToken string `json:"accessToken" validate:"required"` + Requester *request_client.RequestClient +} + +func NewBusinessClient(config *BusinessClientConfig) *BusinessClient { + return &BusinessClient{ + BusinessAccountId: config.BusinessAccountId, + AccessToken: config.AccessToken, + PhoneNumber: manager.NewPhoneNumberManager(&manager.PhoneNumberManagerConfig{ + BusinessAccountId: config.BusinessAccountId, + ApiAccessToken: config.AccessToken, + Requester: config.Requester, + }), + Template: manager.NewTemplateManager(&manager.TemplateManagerConfig{ + BusinessAccountId: config.BusinessAccountId, + ApiAccessToken: config.AccessToken, + Requester: config.Requester, + }), + requester: config.Requester, + } +} + +func (bc *BusinessClient) GetBusinessId() string { + return bc.BusinessAccountId +} + +func (bc *BusinessClient) SetBusinessId(id string) { + bc.BusinessAccountId = id +} + +type WhatsappBusinessAccount struct { + BusinessVerificationStatus string `json:"business_verification_status,omitempty"` + Country string `json:"country,omitempty"` + Currency string `json:"currency,omitempty"` + IsTemplateAnalyticsEnabled string `json:"is_enabled_for_insights,omitempty"` + MessageTemplateNamespace string `json:"message_template_namespace,omitempty"` + Name string `json:"name,omitempty"` + OwnershipType string `json:"ownership_type,omitempty"` + PrimaryFundingId string `json:"primary_funding_id,omitempty"` + PurchaseOrderNumber string `json:"purchase_order_number,omitempty"` + TimezoneId string `json:"timezone_id,omitempty"` +} + +type FetchBusinessAccountResponse struct { + Id string `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + TimezoneId string `json:"timezone_id" validate:"required"` + MessageTemplateNamespace string `json:"message_template_namespace" validate:"required"` +} + +func (client *BusinessClient) Fetch() FetchBusinessAccountResponse { + apiRequest := client.requester.NewApiRequest(client.BusinessAccountId, http.MethodGet) + response, err := apiRequest.Execute() + if err != nil { + // return wapi.go custom error here + fmt.Println("Error while fetching business account", err) + } + var responseToReturn FetchBusinessAccountResponse + json.Unmarshal([]byte(response), &responseToReturn) + return responseToReturn +} + +type AnalyticsRequestGranularityType string + +const ( + AnalyticsRequestGranularityTypeHalfHour AnalyticsRequestGranularityType = "HALF_HOUR" + AnalyticsRequestGranularityTypeDay AnalyticsRequestGranularityType = "DAY" + AnalyticsRequestGranularityTypeMonth AnalyticsRequestGranularityType = "MONTH" +) + +type WhatsAppBusinessAccountAnalyticsProductType int + +const ( + WhatsAppBusinessAccountAnalyticsProductTypeNotificationMessages WhatsAppBusinessAccountAnalyticsProductType = 0 + WhatsAppBusinessAccountAnalyticsProductTypeCustomerSupportMessages WhatsAppBusinessAccountAnalyticsProductType = 2 +) + +type AccountAnalyticsOptions struct { + Start time.Time `json:"start" validate:"required"` + End time.Time `json:"end" validate:"required"` + Granularity AnalyticsRequestGranularityType `json:"granularity" validate:"required"` + PhoneNumbers []string `json:"phone_numbers,omitempty"` + + // * NOT SUPPORTED AS OF NOW + // ProductTypes []WhatsAppBusinessAccountAnalyticsProductType `json:"product_types,omitempty"` + CountryCodes []string `json:"country_codes,omitempty"` +} + +type AnalyticsDataPoint struct { + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + Sent int `json:"sent,omitempty"` + Delivered int `json:"delivered,omitempty"` +} + +type WhatsappBusinessAccountAnalyticsResponse struct { + PhoneNumbers []string `json:"phone_numbers,omitempty"` + Granularity string `json:"granularity,omitempty"` + DataPoints []AnalyticsDataPoint `json:"data_points,omitempty"` +} + +func (client *BusinessClient) FetchAnalytics(options AccountAnalyticsOptions) { + apiRequest := client.requester.NewApiRequest(client.BusinessAccountId, http.MethodGet) + analyticsField := apiRequest.AddField(request_client.ApiRequestQueryParamField{ + Name: "analytics", + Filters: map[string]string{}, + }) + analyticsField.AddFilter("start", fmt.Sprint(options.Start.Unix())) + analyticsField.AddFilter("end", fmt.Sprint(options.End.Unix())) + analyticsField.AddFilter("granularity", string(options.Granularity)) + + if len(options.PhoneNumbers) > 0 { + // get specific phone numbers + analyticsField.AddFilter("phone_numbers", strings.Join(options.PhoneNumbers, ",")) + } else { + // get all phone numbers + analyticsField.AddFilter("phone_numbers", "[]") + } + + if len(options.CountryCodes) > 0 { + analyticsField.AddFilter("country_codes", strings.Join(options.CountryCodes, ",")) + } else { + // get all country codes + analyticsField.AddFilter("country_codes", "[]") + } + response, err := apiRequest.Execute() + if err != nil { + // return wapi.go custom error here + fmt.Println("Error while fetching business account", err) + } + var responseToReturn WhatsappBusinessAccountAnalyticsResponse + json.Unmarshal([]byte(response), &responseToReturn) + fmt.Println("Response to return is", responseToReturn) +} + +type ConversationCategoryType string + +const ( + ConversationCategoryTypeAuthentication ConversationCategoryType = "AUTHENTICATION" + ConversationCategoryTypeMarketing ConversationCategoryType = "MARKETING" + ConversationCategoryTypeService ConversationCategoryType = "SERVICE" + ConversationCategoryTypeUtility ConversationCategoryType = "UTILITY" +) + +type ConversationType string + +const ( + ConversationTypeFreeEntry ConversationType = "FREE_ENTRY" + ConversationTypeFreeTier ConversationType = "FREE_TIER" + ConversationTypeRegular ConversationType = "REGULAR" +) + +type ConversationDirection string + +const ( + ConversationDirectionBusinessInitiated ConversationDirection = "BUSINESS_INITIATED" + ConversationDirectionUserInitiated ConversationDirection = "USER_INITIATED" +) + +type ConversationDimensionType string + +const ( + ConversationDimensionTypeConversationCategory ConversationDimensionType = "CONVERSATION_CATEGORY" + ConversationDimensionTypeConversationDirection ConversationDimensionType = "CONVERSATION_DIRECTION" + ConversationDimensionTypeConversationType ConversationDimensionType = "CONVERSATION_TYPE" + ConversationDimensionTypeCountry ConversationDimensionType = "COUNTRY" + ConversationDimensionTypePhone ConversationDimensionType = "PHONE" +) + +type ConversationAnalyticsGranularityType string + +const ( + ConversationAnalyticsGranularityTypeHalfHour ConversationAnalyticsGranularityType = "HALF_HOUR" + ConversationAnalyticsGranularityTypeDay ConversationAnalyticsGranularityType = "DAILY" + ConversationAnalyticsGranularityTypeMonth ConversationAnalyticsGranularityType = "MONTHLY" +) + +type ConversationAnalyticsOptions struct { + Start time.Time `json:"start" validate:"required"` + End time.Time `json:"end" validate:"required"` + Granularity ConversationAnalyticsGranularityType `json:"granularity" validate:"required"` + PhoneNumbers []string `json:"phone_numbers,omitempty"` + + ConversationCategory []ConversationCategoryType `json:"conversation_category,omitempty"` + ConversationTypes []ConversationCategoryType `json:"conversation_types,omitempty"` + ConversationDirection []ConversationDirection `json:"conversation_direction,omitempty"` + Dimensions []ConversationDimensionType `json:"dimensions,omitempty"` +} + +type WhatsAppConversationAnalyticsNode struct { + Start int `json:"start" validate:"required"` + End int `json:"end,omitempty" validate:"required"` + Conversation int `json:"conversation,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + Country string `json:"country,omitempty"` + ConversationType string `json:"conversation_type,omitempty"` + ConversationDirection string `json:"conversation_direction,omitempty"` + ConversationCategory string `json:"conversation_category,omitempty"` + Cost int `json:"cost,omitempty"` +} + +type WhatsAppConversationAnalyticsEdge struct { + Data []struct { + DataPoints []WhatsAppConversationAnalyticsNode `json:"data_points,omitempty"` + } `json:"data,omitempty"` + Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"` +} + +type WhatsAppConversationAnalyticsResponse struct { + ConversationAnalytics []WhatsAppConversationAnalyticsEdge `json:"conversation_analytics" validate:"required"` +} + +func (client *BusinessClient) ConversationAnalytics(options ConversationAnalyticsOptions) (*WhatsAppConversationAnalyticsResponse, error) { + apiRequest := client.requester.NewApiRequest(client.BusinessAccountId, http.MethodGet) + analyticsField := apiRequest.AddField(request_client.ApiRequestQueryParamField{ + Name: "conversation_analytics", + Filters: map[string]string{}, + }) + analyticsField.AddFilter("start", fmt.Sprint(options.Start.Unix())) + analyticsField.AddFilter("end", fmt.Sprint(options.End.Unix())) + analyticsField.AddFilter("granularity", string(options.Granularity)) + + if len(options.PhoneNumbers) > 0 { + // get specific phone numbers + analyticsField.AddFilter("phone_numbers", strings.Join(options.PhoneNumbers, ",")) + } else { + // get all phone numbers + analyticsField.AddFilter("phone_numbers", "[]") + } + + if len(options.ConversationCategory) > 0 { + categoryStrings := make([]string, len(options.ConversationCategory)) + for i, category := range options.ConversationCategory { + categoryStrings[i] = string(category) + } + analyticsField.AddFilter("conversation_category", strings.Join(categoryStrings, ",")) + } else { + analyticsField.AddFilter("conversation_category", "[]") // Empty slice + } + + if len(options.ConversationTypes) > 0 { + typeStrings := make([]string, len(options.ConversationTypes)) + for i, ctype := range options.ConversationTypes { + typeStrings[i] = string(ctype) + } + analyticsField.AddFilter("conversation_types", strings.Join(typeStrings, ",")) + } else { + analyticsField.AddFilter("conversation_types", "[]") // Empty slice + } + + if len(options.ConversationDirection) > 0 { + directionStrings := make([]string, len(options.ConversationDirection)) + for i, direction := range options.ConversationDirection { + directionStrings[i] = string(direction) + } + analyticsField.AddFilter("conversation_direction", strings.Join(directionStrings, ",")) + } else { + analyticsField.AddFilter("conversation_direction", "[]") // Empty slice + } + + if len(options.Dimensions) > 0 { + dimensionsStrings := make([]string, len(options.Dimensions)) + for i, dim := range options.Dimensions { + dimensionsStrings[i] = string(dim) + } + analyticsField.AddFilter("dimensions", strings.Join(dimensionsStrings, ",")) + } else { + // get all country codes + analyticsField.AddFilter("dimensions", "[]") + } + + response, err := apiRequest.Execute() + if err != nil { + // return wapi.go custom error here + fmt.Println("Error while fetching business account", err) + } + var responseToReturn WhatsAppConversationAnalyticsResponse + json.Unmarshal([]byte(response), &responseToReturn) + + fmt.Println("Response to return is", responseToReturn) + + return &responseToReturn, nil +} + +func (client *BusinessClient) FetchAllProductCatalogs() (string, error) { + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/product_catalogs/#Reading + apiRequest := client.requester.NewApiRequest(strings.Join([]string{client.BusinessAccountId, "product_catalogs"}, "/"), http.MethodGet) + response, err := apiRequest.Execute() + return response, err + +} + +func (client *BusinessClient) CreateNewProductCatalog() (string, error) { + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/product_catalogs/#Creating + apiRequest := client.requester.NewApiRequest(strings.Join([]string{client.BusinessAccountId, "product_catalogs"}, "/"), http.MethodPost) + response, err := apiRequest.Execute() + return response, err +} + +type BusinessRole string + +const ( + BusinessRoleManage BusinessRole = "MANAGE" + BusinessRoleDevelop BusinessRole = "DEVELOP" + BusinessRoleManageTemplates BusinessRole = "MANAGE_TEMPLATES" + BusinessRoleManagePhone BusinessRole = "MANAGE_PHONE" + BusinessRoleViewCost BusinessRole = "VIEW_COST" + BusinessRoleManageExtensions BusinessRole = "MANAGE_EXTENSIONS" + BusinessRoleViewPhoneAssets BusinessRole = "VIEW_PHONE_ASSETS" + BusinessRoleManagePhoneAssets BusinessRole = "MANAGE_PHONE_ASSETS" + BusinessRoleViewTemplates BusinessRole = "VIEW_TEMPLATES" + BusinessRoleMessaging BusinessRole = "MESSAGING" + BusinessRoleManageBusinessPhones BusinessRole = "MANAGE_BUSINESS_PHONES" +) + +func (role *BusinessRole) String() string { + return string(*role) +} + +func (client *BusinessClient) UpdateUser(userId string, tasks []BusinessRole) (string, error) { + + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/#Updating + + apiRequest := client.requester.NewApiRequest(strings.Join([]string{client.BusinessAccountId, "assigned_users."}, "/"), http.MethodPost) + apiRequest.AddQueryParam("user", userId) + roles := make([]string, len(tasks)) + for i, task := range tasks { + roles[i] = task.String() + } + apiRequest.AddQueryParam("tasks", strings.Join(roles, ",")) + + response, err := apiRequest.Execute() + + return response, err + +} + +func (client *BusinessClient) DeleteUser(userId string) (string, error) { + + // https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/#Deleting + + apiRequest := client.requester.NewApiRequest(strings.Join([]string{client.BusinessAccountId, "assigned_users."}, "/"), http.MethodDelete) + apiRequest.AddQueryParam("user", userId) + response, err := apiRequest.Execute() + return response, err + +} diff --git a/pkg/client/client.go b/pkg/client/client.go index c2170b4..0c11103 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -1,75 +1,68 @@ package wapi import ( - "fmt" - "github.com/labstack/echo/v4" - "github.com/sarthakjdev/wapi.go/internal" "github.com/sarthakjdev/wapi.go/internal/manager" - requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/pkg/business" "github.com/sarthakjdev/wapi.go/pkg/events" + "github.com/sarthakjdev/wapi.go/pkg/messaging" ) -// Client represents a WhatsApp client. -type Client struct { - Media manager.MediaManager - Message manager.MessageManager - webhook manager.WebhookManager - phoneNumberId string - apiAccessToken string - businessAccountId string -} - -// ClientConfig represents the configuration options for the WhatsApp client. type ClientConfig struct { - PhoneNumberId string `validate:"required"` - ApiAccessToken string `validate:"required"` - BusinessAccountId string `validate:"required"` - WebhookPath string `validate:"required"` + BusinessAccountId string + ApiAccessToken string WebhookSecret string `validate:"required"` + + // these two are not required, because may be user want to use their own server + WebhookPath string WebhookServerPort int } -// NewWapiClient creates a new instance of Client. -func New(configs ClientConfig) (*Client, error) { - // Validate the client configuration options - err := internal.GetValidator().Struct(configs) - if err != nil { - return nil, fmt.Errorf("error validating client config: %w", err) - } +type Client struct { + Business business.BusinessClient // Business is the business client. + Messaging []messaging.MessagingClient // MessagingClient is the messaging client. + eventManager *manager.EventManager // eventManager is the event manager. + webhook *manager.WebhookManager // webhook is the webhook manager. + requester *request_client.RequestClient - // Create a new request client - requester := *requestclient.NewRequestClient(configs.PhoneNumberId, configs.ApiAccessToken) + apiAccessToken string + businessAccountId string +} - // Create a new event manager +func New(config *ClientConfig) *Client { eventManager := *manager.NewEventManager() - - // Create a new Client instance with the provided configurations + requester := *request_client.NewRequestClient(config.ApiAccessToken) return &Client{ - Media: *manager.NewMediaManager(requester), - Message: *manager.NewMessageManager(requester), - webhook: *manager.NewWebhook(&manager.WebhookManagerConfig{Path: configs.WebhookPath, Secret: configs.WebhookSecret, Port: configs.WebhookServerPort, EventManager: eventManager, Requester: requester}), - phoneNumberId: configs.PhoneNumberId, - apiAccessToken: configs.ApiAccessToken, - businessAccountId: configs.BusinessAccountId, - }, nil + businessAccountId: config.BusinessAccountId, + apiAccessToken: config.BusinessAccountId, + Messaging: []messaging.MessagingClient{}, + eventManager: &eventManager, + Business: *business.NewBusinessClient(&business.BusinessClientConfig{ + BusinessAccountId: config.BusinessAccountId, + AccessToken: config.ApiAccessToken, + Requester: &requester, + }), + webhook: manager.NewWebhook(&manager.WebhookManagerConfig{Path: config.WebhookPath, Secret: config.WebhookSecret, Port: config.WebhookServerPort, EventManager: eventManager, Requester: requester}), + requester: &requester, + } } -// GetPhoneNumberId returns the phone number ID associated with the client. -func (client *Client) GetPhoneNumberId() string { - return client.phoneNumberId -} +func (client *Client) NewMessagingClient(phoneNumberId string) *messaging.MessagingClient { + // Create a new request client -// SetPhoneNumberId sets the phone number ID for the client. -func (client *Client) SetPhoneNumberId(phoneNumberId string) { - client.phoneNumberId = phoneNumberId -} + // Create a new Client instance with the provided configurations + messagingClient := &messaging.MessagingClient{ + Media: *manager.NewMediaManager(*client.requester), + Message: *manager.NewMessageManager(*client.requester, phoneNumberId), + PhoneNumberId: phoneNumberId, + ApiAccessToken: client.apiAccessToken, + BusinessAccountId: client.businessAccountId, + Requester: client.requester, + } -// InitiateClient initializes the client and starts listening to events from the webhook. -// It returns true if the client was successfully initiated. -func (client *Client) InitiateClient() bool { - client.webhook.ListenToEvents() - return true + client.Messaging = append(client.Messaging, *messagingClient) + return messagingClient } // GetWebhookGetRequestHandler returns the handler function for handling GET requests to the webhook. @@ -84,5 +77,13 @@ func (client *Client) GetWebhookPostRequestHandler() func(c echo.Context) error // OnMessage registers a handler for a specific event type. func (client *Client) On(eventType events.EventType, handler func(events.BaseEvent)) { - client.webhook.EventManager.On(eventType, handler) + client.webhook. + EventManager.On(eventType, handler) +} + +// InitiateClient initializes the client and starts listening to events from the webhook. +// It returns true if the client was successfully initiated. +func (client *Client) Initiate() bool { + client.webhook.ListenToEvents() + return true } diff --git a/pkg/events/audio_message_event.go b/pkg/events/audio_message_event.go index 71ed1f5..c015648 100644 --- a/pkg/events/audio_message_event.go +++ b/pkg/events/audio_message_event.go @@ -11,7 +11,7 @@ type AudioMessageEvent struct { } // NewAudioMessageEvent creates a new AudioMessageEvent instance. -func NewAudioMessageEvent(baseMessageEvent BaseMessageEvent, mediaId string, audio components.AudioMessage, mime_type, sha256, mimeType string) *AudioMessageEvent { +func NewAudioMessageEvent(baseMessageEvent BaseMessageEvent, audio components.AudioMessage, mimeType, sha256, mediaId string) *AudioMessageEvent { return &AudioMessageEvent{ BaseMediaMessageEvent: BaseMediaMessageEvent{ BaseMessageEvent: baseMessageEvent, diff --git a/pkg/events/base_event.go b/pkg/events/base_event.go index fd6b60e..7d9c8ca 100644 --- a/pkg/events/base_event.go +++ b/pkg/events/base_event.go @@ -1,7 +1,10 @@ package events import ( - requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "net/http" + "strings" + + "github.com/sarthakjdev/wapi.go/internal/request_client" "github.com/sarthakjdev/wapi.go/pkg/components" ) @@ -24,14 +27,15 @@ type BaseSystemEventInterface interface { } type BaseMessageEvent struct { - requester requestclient.RequestClient + requester request_client.RequestClient MessageId string `json:"message_id"` Context MessageContext `json:"context"` Timestamp string `json:"timestamp"` IsForwarded bool `json:"is_forwarded"` + PhoneNumber string `json:"phone_number"` } -func NewBaseMessageEvent(messageId string, timestamp string, from string, isForwarded bool, requester requestclient.RequestClient) BaseMessageEvent { +func NewBaseMessageEvent(phoneNumber string, messageId string, timestamp string, from string, isForwarded bool, requester request_client.RequestClient) BaseMessageEvent { return BaseMessageEvent{ MessageId: messageId, Context: MessageContext{ @@ -40,6 +44,7 @@ func NewBaseMessageEvent(messageId string, timestamp string, from string, isForw requester: requester, Timestamp: timestamp, IsForwarded: isForwarded, + PhoneNumber: phoneNumber, } } @@ -49,7 +54,6 @@ func (bme BaseMessageEvent) GetEventType() string { // Reply to the message func (baseMessageEvent *BaseMessageEvent) Reply(Message components.BaseMessage) (string, error) { - body, err := Message.ToJson(components.ApiCompatibleJsonConverterConfigs{ SendToPhoneNumber: baseMessageEvent.Context.From, ReplyToMessageId: baseMessageEvent.MessageId, @@ -59,10 +63,9 @@ func (baseMessageEvent *BaseMessageEvent) Reply(Message components.BaseMessage) return "", err } - baseMessageEvent.requester.RequestCloudApi(requestclient.RequestCloudApiParams{ - Body: string(body), - Path: "/" + baseMessageEvent.requester.PhoneNumberId + "/messages", - }) + apiRequest := baseMessageEvent.requester.NewApiRequest(strings.Join([]string{baseMessageEvent.PhoneNumber, "messages"}, "/"), http.MethodPost) + apiRequest.SetBody(string(body)) + apiRequest.Execute() return "", nil diff --git a/pkg/events/customer_number_change_event.go b/pkg/events/customer_number_change_event.go index 43fc931..a61dd35 100644 --- a/pkg/events/customer_number_change_event.go +++ b/pkg/events/customer_number_change_event.go @@ -1,7 +1,7 @@ package events type CustomerNumberChangedEvent struct { - BaseMessageEvent `json:",inline"` + BaseSystemEvent `json:",inline"` ChangeDescription string `json:"changeDescription"` NewWaId string `json:"newWaId"` OldWaId string `json:"oldWaId"` diff --git a/pkg/messaging/client.go b/pkg/messaging/client.go new file mode 100644 index 0000000..7a21bfd --- /dev/null +++ b/pkg/messaging/client.go @@ -0,0 +1,71 @@ +package messaging + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/sarthakjdev/wapi.go/internal/manager" + "github.com/sarthakjdev/wapi.go/internal/request_client" +) + +// MessagingClient represents a WhatsApp client. +type MessagingClient struct { + Media manager.MediaManager + Message manager.MessageManager + PhoneNumberId string + ApiAccessToken string + BusinessAccountId string + Requester *request_client.RequestClient +} + +// GetPhoneNumberId returns the phone number ID associated with the client. +func (client *MessagingClient) GetPhoneNumberId() string { + return client.PhoneNumberId +} + +// SetPhoneNumberId sets the phone number ID for the client. +func (client *MessagingClient) SetPhoneNumberId(phoneNumberId string) { + client.PhoneNumberId = phoneNumberId +} + +func (client *MessagingClient) GetApiAccessToken() string { + return client.ApiAccessToken +} + +func (client *MessagingClient) SetApiAccessToken(apiAccessToken string) { + client.ApiAccessToken = apiAccessToken +} + +func (client *MessagingClient) GetBusinessAccountId() string { + return client.BusinessAccountId +} + +type RegisterResponse struct { + Success bool `json:"success"` +} + +// this register function is for one time registration of the phone number to enable the usage with WhatsApp Cloud API +func (client *MessagingClient) Register(pin string) (RegisterResponse, error) { + apiRequest := client.Requester.NewApiRequest(strings.Join([]string{client.PhoneNumberId, "resgiter"}, "/"), http.MethodPost) + apiRequest.AddQueryParam("messaging_product", "WHATSAPP") + apiRequest.AddQueryParam("pin", pin) + response, err := apiRequest.Execute() + if err != nil { + return RegisterResponse{}, err + } + var registerResponse RegisterResponse + json.Unmarshal([]byte(response), ®isterResponse) + return registerResponse, nil +} + +func (client *MessagingClient) Deregister() (RegisterResponse, error) { + apiRequest := client.Requester.NewApiRequest(strings.Join([]string{client.PhoneNumberId, "deregister"}, "/"), http.MethodPost) + response, err := apiRequest.Execute() + if err != nil { + return RegisterResponse{}, err + } + var registerResponse RegisterResponse + json.Unmarshal([]byte(response), ®isterResponse) + return registerResponse, nil +}