diff --git a/.github/COMMIT_CONVENTION.md b/.github/COMMIT_CONVENTION.md new file mode 100644 index 0000000..21461c3 --- /dev/null +++ b/.github/COMMIT_CONVENTION.md @@ -0,0 +1,98 @@ +# ๐Ÿ“˜ Commit and Branch Conventions + +At `wapi.go`, we follow a strict set of conventions for commit messages and branch names to ensure that our repository stays organized, and our commit history remains crystal clear. Here's a guide on our conventions: + +--- + +## ๐Ÿš€ Conventional Commits + +Conventional commits ensure our commit messages are clear and useful. + +๐Ÿ“‹ **Benefits**: + +- ๐Ÿ“œ Automatic changelog generation +- ๐Ÿ”ข Simplified versioning +- ๐Ÿง Improved readability of commit messages + +### ๐Ÿ“„ Commit Message Format + +Each commit message should follow this format: + +``` +(): + + + + +``` + +- **``**: Describes the purpose of the commit: + - ๐Ÿ†• `feat`: Introduces a new feature + - ๐Ÿž `fix`: Addresses a bug + - ๐Ÿ“š `docs`: Updates documentation + - ๐ŸŽจ `style`: Code that relates to styling, not affecting logic + - ๐Ÿ”ง `refactor`: Refactoring existing code + - ๐Ÿš€ `perf`: Improving performance + - ๐Ÿงช `test`: All about tests + - ๐Ÿงฝ `chore`: Maintenance tasks +- **``**: (Optional) Specifies which part of the codebase is affected. + +- **``**: A concise summary of the changes made. + +### ๐Ÿ“ Examples: + +1. Introducing a new feature: + +``` +feat(auth): implement social login +``` + +2. Addressing a bug: + +``` +fix(button): resolve alignment issue +``` + +--- + +## ๐ŸŒฒ Conventional Branching + +A standardized naming system for branches helps everyone quickly understand a branch's purpose. + +### ๐Ÿ“„ Branch Naming Format + +Branch names should adhere to: + +``` +/ +``` + +- **``**: Purpose of the branch, common ones being: + + - ๐Ÿ†• `feature`: Developing a new feature. + - ๐Ÿž `fix`: Addressing a bug. + - ๐Ÿงฝ `chore`: Regular maintenance tasks. + - ๐Ÿ”ฅ `hotfix`: Immediate fixes, often tied to production issues. + - ๐Ÿ“š `docs`: Documentation enhancements. + +- **``**: A brief, kebab-cased (words separated by hyphens) description of the branch's objective. + +### ๐Ÿ“ Examples: + +1. Developing a new user dashboard: + +``` +feature/user-dashboard +``` + +2. Resolving a login issue: + +``` +fix/login-issue +``` + +--- + +๐Ÿ™Œ Thanks for contributing to `wapi.go`! By adhering to these conventions, we're making our repository a better place. If you're new, welcome aboard, and if you've been here, thanks for sticking around! + +--- diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9b77ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] + +**Smartphone (please complete the following information):** + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..46be629 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discord server + url: https://discord.gg/PytnZqWpeQ + about: Please visit our Discord server for questions and support requests. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..babf9b2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,7 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: "" +labels: "" +assignees: "" +--- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2bc5d5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2dcc071 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ +**Please describe the changes this PR makes and why it should be merged:** + +**Status and versioning classification:** diff --git a/.github/workflows/auto-comment.yaml b/.github/workflows/auto-comment.yaml new file mode 100644 index 0000000..17b974a --- /dev/null +++ b/.github/workflows/auto-comment.yaml @@ -0,0 +1,18 @@ +name: Auto Comment on New Issue Label +# This workflow is triggered when a label is added to an issue. +on: + issues: + types: [labeled] + +jobs: + processLabelAction: + name: Process Label Action + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Process Label Action + uses: hramos/respond-to-issue-based-on-label@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: ".github/label-actions.yml" diff --git a/.github/workflows/formatTag/action.yml b/.github/workflows/formatTag/action.yml new file mode 100644 index 0000000..baef702 --- /dev/null +++ b/.github/workflows/formatTag/action.yml @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..0472645 --- /dev/null +++ b/.github/workflows/formatTag/formatTag.js @@ -0,0 +1,22 @@ +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 new file mode 100644 index 0000000..5ddc8d9 --- /dev/null +++ b/.github/workflows/formatTag/index.js @@ -0,0 +1,11 @@ +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/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml new file mode 100644 index 0000000..5b99bed --- /dev/null +++ b/.github/workflows/validate-pr-title.yml @@ -0,0 +1,30 @@ +name: Check PR title + +on: + pull_request: + types: + - opened + - reopened + - edited + - synchronize + - review_requested + - auto_merge_enabled + +concurrency: + group: check-pr-title-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + validate-pr-title: + name: Validate PR title + runs-on: ubuntu-latest + permissions: + statuses: write + timeout-minutes: 10 + steps: + - uses: aslafy-z/conventional-pr-title-action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + success-state: "PR Title follows the Conventional Commits spec" + failure-state: "PR Title does not follow the Conventional Commits spec" diff --git a/.gitignore b/.gitignore index 5086c50..077bbdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ node_modules .vscode/ -config.toml -node_modules dist/* \ No newline at end of file diff --git a/.husky/hooks/commit-msg b/.husky/hooks/commit-msg new file mode 100755 index 0000000..f37eba3 --- /dev/null +++ b/.husky/hooks/commit-msg @@ -0,0 +1,22 @@ +#!/bin/sh + +# # Check for conventional commit format +# commit_msg=$(cat "$1") +# # Modified regex to allow for more valid formats +# conventional_commit_regex='^(feat|fix|chore|docs|test|style|refactor|perf|build|ci|revert)(\(.+\))?(!)?(:)(\s.*)?$' + +# echo "$conventional_commit_regex" + +# if ! [[ "$commit_msg" =~ $conventional_commit_regex ]]; then +# echo "ERROR: Commit message does not adhere to Conventional Commits format:" +# echo "$commit_msg" +# echo "$($commit_msg =~ $conventional_commit_regex )" +# exit 1 +# fi + +# If not a merge commit, require commit to be signed off +if [ ! -e .git/MERGE_MSG ] && ! grep -q "^Signed-off-by: " "$1"; then + echo >&2 "Commit message must be signed off with your user name and email." + echo >&2 "To sign off your commit, add the -s flag to the git commit command." + exit 1 +fi diff --git a/.husky/hooks/pre-commit b/.husky/hooks/pre-commit new file mode 100755 index 0000000..35d2694 --- /dev/null +++ b/.husky/hooks/pre-commit @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +branch=$(git symbolic-ref HEAD) + +if [ "$branch" = "refs/heads/master" ]; then + echo "\\033[31mDirect commit to master is not allowed.\\033[0m" + exit 1 +fi + +go fmt ./... diff --git a/.husky/hooks/pre-push b/.husky/hooks/pre-push new file mode 100755 index 0000000..864d02f --- /dev/null +++ b/.husky/hooks/pre-push @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +branch=$(git symbolic-ref HEAD) + +if [ "$branch" = "refs/heads/master" ]; then + echo "\\033[31mDirect push to master is not allowed.\\033[0m" + exit 1 +fi diff --git a/README.md b/README.md index 3649845..9cd72dc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,99 @@ +
+
+

+@wapijs/wapi.js +

+
+
+ +## ๐Ÿ“Œ 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). + ## ๐Ÿ“– About -a Golang module, designed to interact with the WhatsApp cloud API in a user-friendly manner. +Wapi.js is a JavaScript module, written in TypeScript, designed to interact with the WhatsApp cloud API in a user-friendly manner. + +## โœจ Features + +- Single Client Model +- Send Messages with the least configuration +- Event Listener for Notifications (support both User and System Notifications) +- Upload Media to WhatsApp servers +- Reply and React to incoming messages. + +## ๐Ÿ’ป Installation + +This assumes you already have a working Go environment, if not please see +[this page](https://golang.org/doc/install) first. + +`go get` _will always pull the latest tagged release from the master branch._ + +```sh +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. + +## ๐Ÿš€ Usage + +You can check out the example WhatsApp bot here. [Example Chatbot](./example-chat-bot/) + +# Example Usage + +Import the package into your project. +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/events + +```go +import "github.com/sarthakjdev/wapi.go/wapi/wapi" +``` + +Construct a new Wapi Client to access the managers in order to send messages and listen to incoming notifications. + +```go +whatsappClient, err := wapi.New(wapi.ClientConfig{ + PhoneNumberId: "", + ApiAccessToken: "", + BusinessAccountId: "", + WebhookPath: "/webhook", + WebhookSecret: "", + WebhookServerPort: 8080, + }) +``` + +## ๐Ÿ”— References + +- **Message Structures**: Refer to the WhatsApp Docs [here](https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages). + +- **Notification Payloads**: Details can be found [here](https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components). + + + +## ๐Ÿค Contribution Guidelines + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +For detailed guidelines, check [Contributing.md](./CONTRIBUTING.md). + +## ๐Ÿ“œ License + +Distributed under the Apache 2.0 License. View [LICENSE](./LICENSE). + +## ๐Ÿ“ž Contact + +- [Sarthak Jain](https://sarthakjdev.com) +- 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. diff --git a/cmd/example-chat-bot/main.go b/cmd/example-chat-bot/main.go deleted file mode 100644 index cd33a2c..0000000 --- a/cmd/example-chat-bot/main.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/sarthakjdev/wapi.go/pkg/client" -) - -func main() { - fmt.Println("This is the main package entry point of my golang file") - whatsappClient := client.NewClient() - fmt.Print(whatsappClient) -} diff --git a/example-chat-bot/main.go b/example-chat-bot/main.go new file mode 100644 index 0000000..2d619dd --- /dev/null +++ b/example-chat-bot/main.go @@ -0,0 +1,154 @@ +package main + +import ( + "fmt" + + "github.com/sarthakjdev/wapi.go/internal/manager" + wapi "github.com/sarthakjdev/wapi.go/pkg/client" + wapiComponents "github.com/sarthakjdev/wapi.go/pkg/components" + "github.com/sarthakjdev/wapi.go/pkg/events" +) + +func main() { + + // ! TODO: programmatic read the env variables here + + // creating a client + whatsappClient, err := wapi.New(wapi.ClientConfig{ + PhoneNumberId: "113269274970227", + ApiAccessToken: "EABhCftGVaeIBO27nfwWbjXQWIxFbXFbcRLNbtavOmdGQSZBq7cQ0L6pqACzW2VZBEy53fUohFJQNLdVqeS4hnthQrKS2X9d0Wm1rXih7ej8nkEUjxI0odIq3VLCjlD2RaPK7ZA0BA0lBhkDVmoDbQX7cUIObu6yqUvY2rsDiKZCZA6qNQocjU40e0z6a97kjbt3t7Inkp5R55BSF8uzVEv5zKN0ZBU71Rap7aEoZBplAsjy", + BusinessAccountId: "103043282674158", + WebhookPath: "/webhook", + WebhookSecret: "1234567890", + WebhookServerPort: 8080, + }) + + if err != nil { + fmt.Println("error creating client", err) + return + } + + // create a message + textMessage, err := wapiComponents.NewTextMessage(wapiComponents.TextMessageConfigs{ + Text: "Hello, from wapi.go", + }) + + if err != nil { + fmt.Println("error creating text message", err) + return + } + + // contactMessage, err := wapiComponents.NewContactMessage([]wapiComponents.Contact{ + // *wapiComponents.NewContact(wapiComponents.ContactName{ + // FormattedName: "Sarthak Jain", + // FirstName: "Sarthak", + // })}) + + // if err != nil { + // fmt.Println("error creating contact message", err) + // return + // } + + // locationMessage, err := wapiComponents.NewLocationMessage(28.7041, 77.1025) + + // if err != nil { + // fmt.Println("error creating location message", err) + // return + // } + + // reactionMessage, err := wapiComponents.NewReactionMessage(wapiComponents.ReactionMessageParams{ + // MessageId: "wamid.HBgMOTE5NjQzNTAwNTQ1FQIAERgSQzVGOTlFMzExQ0VCQTg0MUFCAA==", + // Emoji: "๐Ÿ˜", + // }) + + // if err != nil { + // fmt.Println("error creating reaction message", err) + // return + // } + + // send the message + // whatsappClient.Message.Send(manager.SendMessageParams{Message: textMessage, PhoneNumber: "919643500545"}) + // whatsappClient.Message.Send(manager.SendMessageParams{Message: contactMessage, PhoneNumber: "919643500545"}) + // whatsappClient.Message.Send(manager.SendMessageParams{Message: locationMessage, PhoneNumber: "919643500545"}) + // whatsappClient.Message.Send(manager.SendMessageParams{Message: reactionMessage, PhoneNumber: "919643500545"}) + + // listMessage, err := wapiComponents.NewListMessage(wapiComponents.ListMessageParams{ + // ButtonText: "Button 1", + // BodyText: "Body 1", + // }) + + // if err != nil { + // fmt.Println("error creating list message", err) + // return + // } + + // listSectionRow, err := wapiComponents.NewListSectionRow("1", "Title 1", "Description 1") + + // if err != nil { + // fmt.Println("error creating list section row", err) + // return + // } + + // listSection, err := wapiComponents.NewListSection("Section1") + + // if err != nil { + // fmt.Println("error creating list section row", err) + // return + // } + + // listSection.AddRow(listSectionRow) + // listMessage.AddSection(listSection) + // jsonData, err := listMessage.ToJson(wapiComponents.ApiCompatibleJsonConverterConfigs{SendToPhoneNumber: "919643500545"}) + + // if err != nil { + // fmt.Println("error converting message to json", err) + // return + // } + + // fmt.Println(string(jsonData)) + + // whatsappClient.Message.Send(manager.SendMessageParams{Message: listMessage, PhoneNumber: "919643500545"}) + + // buttonMessage, err := wapiComponents.NewQuickReplyButtonMessage("Body 1") + + // if err != nil { + // fmt.Println("error creating button message", err) + // return + // } + + // buttonMessage.AddButton("1", "Button 1") + // buttonMessage.AddButton("2", "Button 2") + + // whatsappClient.Message.Send(manager.SendMessageParams{Message: buttonMessage, PhoneNumber: "919643500545"}) + + whatsappClient.On(manager.ReadyEvent, func(event events.BaseEvent) { + fmt.Println("client is ready") + }) + + whatsappClient.On(manager.TextMessageEvent, func(event events.BaseEvent) { + fmt.Println("text message event received") + + textMessageEvent := event.(*events.TextMessageEvent) + fmt.Println(textMessageEvent.Context.From) + textMessageEvent.Reply(textMessage) + + }) + + whatsappClient.On(manager.AudioMessageEvent, func(be events.BaseEvent) { + fmt.Println("audio message event received") + }) + + whatsappClient.On(manager.VideoMessageEvent, func(be events.BaseEvent) { + fmt.Println("video message event received") + }) + + whatsappClient.On(manager.DocumentMessageEvent, func(be events.BaseEvent) { + fmt.Println("document message event received") + }) + + whatsappClient.On(manager.ImageMessageEvent, func(be events.BaseEvent) { + fmt.Println("image message event received") + }) + + whatsappClient.InitiateClient() +} diff --git a/go.mod b/go.mod index 25f28dc..195a470 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,24 @@ module github.com/sarthakjdev/wapi.go go 1.21.3 + +require ( + github.com/go-playground/validator/v10 v10.20.0 + github.com/labstack/echo/v4 v4.12.0 +) + +require ( + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8cc48e0 --- /dev/null +++ b/go.sum @@ -0,0 +1,43 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/manager/event_manager.go b/internal/manager/event_manager.go new file mode 100644 index 0000000..1bdeec4 --- /dev/null +++ b/internal/manager/event_manager.go @@ -0,0 +1,81 @@ +package manager + +import ( + "fmt" + "sync" + + "github.com/sarthakjdev/wapi.go/pkg/events" +) + +// ChannelEvent represents an event that can be published and subscribed to. +type ChannelEvent struct { + Type EventType // Type is the type of the event. + Data events.BaseEvent // Data is the data associated with the event. +} + +// EventManger is responsible for managing events and their subscribers. +type EventManger struct { + subscribers map[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{ + subscribers: make(map[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 EventType) (chan ChannelEvent, error) { + em.Lock() + defer em.Unlock() + if ch, ok := em.subscribers[eventName]; ok { + return ch, nil + } + em.subscribers[eventName] = make(chan ChannelEvent, 100) + return em.subscribers[eventName], nil +} + +// Unsubscribe removes a subscriber from the specified event type. +func (em *EventManger) Unsubscribe(id 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(eventType EventType, data events.BaseEvent) error { + fmt.Println("Publishing event: ", eventType) + em.Lock() + defer em.Unlock() + + if ch, ok := em.subscribers[eventType]; ok { + select { + case ch <- ChannelEvent{ + Type: eventType, + Data: data, + }: + default: + return fmt.Errorf("event queue full for type: %s", eventType) + } + } + return nil +} + +// 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 EventType, handler func(events.BaseEvent)) EventType { + ch, _ := em.Subscribe(eventName) + go func() { + for { + select { + case event := <-ch: + handler(event.Data) + } + } + }() + return eventName +} diff --git a/internal/manager/media_manager.go b/internal/manager/media_manager.go new file mode 100644 index 0000000..556eb66 --- /dev/null +++ b/internal/manager/media_manager.go @@ -0,0 +1,25 @@ +package manager + +import requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + +// MediaManager is responsible for managing media related operations. +type MediaManager struct { + requester requestclient.RequestClient +} + +// NewMediaManager creates a new instance of MediaManager. +func NewMediaManager(requester requestclient.RequestClient) *MediaManager { + return &MediaManager{ + requester: requester, + } +} + +// GetMediaUrlById retrieves the media URL by its ID. +func (mm *MediaManager) GetMediaUrlById() { + +} + +// GetMediaIdByUrl retrieves the media ID by its URL. +func (mm *MediaManager) GetMediaIdByUrl() { + +} diff --git a/internal/manager/message_manager.go b/internal/manager/message_manager.go new file mode 100644 index 0000000..7c9eee5 --- /dev/null +++ b/internal/manager/message_manager.go @@ -0,0 +1,49 @@ +package manager + +import ( + "fmt" + + requestclient "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 +} + +// NewMessageManager creates a new instance of MessageManager. +func NewMessageManager(requester requestclient.RequestClient) *MessageManager { + return &MessageManager{ + requester: requester, + } +} + +// SendMessageParams represents the parameters for sending a message. +type SendMessageParams struct { + Message components.BaseMessage + PhoneNumber string +} + +// Send sends a message with the given parameters and returns the response. +// TODO: return the structured response from here +func (mm *MessageManager) Send(params SendMessageParams) (string, error) { + body, err := params.Message.ToJson(components.ApiCompatibleJsonConverterConfigs{ + SendToPhoneNumber: params.PhoneNumber, + // ReplyToMessageId: "wamid.HBgMOTE5NjQzNTAwNTQ1FQIAERgSQzVGOTlFMzExQ0VCQTg0MUFCAA==", + }) + if err != nil { + // 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", + }) + return "ok", nil +} + +// Reply replies to a message. +func (mm *MessageManager) Reply() { + // Reply to message +} diff --git a/internal/manager/types.go b/internal/manager/types.go new file mode 100644 index 0000000..0639ec3 --- /dev/null +++ b/internal/manager/types.go @@ -0,0 +1,334 @@ +package manager + +type EventType string + +const ( + TextMessageEvent EventType = "text_message" + AudioMessageEvent EventType = "audio_message" + VideoMessageEvent EventType = "video_message" + ImageMessageEvent EventType = "image_message" + ContactMessageEvent EventType = "contact_message" + DocumentMessageEvent EventType = "document_message" + LocationMessageEvent EventType = "location_message" + ReactionMessageEvent EventType = "reaction_message" + ListInteractionMessageEvent EventType = "list_interaction_message" + TemplateMessageEvent EventType = "template_message" + QuickReplyMessageEvent EventType = "quick_reply_message" + ReplyButtonInteractionEvent EventType = "reply_button_interaction" + StickerMessageEvent EventType = "sticker_message" + AdInteractionEvent EventType = "ad_interaction_message" + CustomerIdentityChangedEvent EventType = "customer_identity_changed" + CustomerNumberChangedEvent EventType = "customer_number_changed" + MessageDeliveredEvent EventType = "message_delivered" + MessageFailedEvent EventType = "message_failed" + MessageReadEvent EventType = "message_read" + MessageSentEvent EventType = "message_sent" + MessageUndeliveredEvent EventType = "message_undelivered" + OrderReceivedEvent EventType = "order_received" + ProductInquiryEvent EventType = "product_inquiry" + UnknownEvent EventType = "unknown" + ErrorEvent EventType = "error" + WarnEvent EventType = "warn" + ReadyEvent EventType = "ready" +) + +type NotificationReasonEnum string + +const ( + NotificationReasonMessage NotificationReasonEnum = "message" +) + +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 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 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 NotificationPayloadAudioMessageSchemaType struct { + Audio struct { + Id string `json:"id,omitempty"` + MIMEType string `json:"mime_type,omitempty"` + SHA256 string `json:"sha256,omitempty"` + } `json:"audio,omitempty"` +} + +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 NotificationPayloadButtonMessageSchemaType struct { + Button struct { + Payload string `json:"payload"` + Text string `json:"text"` + } `json:"button,omitempty"` +} + +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 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 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 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 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 NotificationPayloadReactionMessageSchemaType struct { + Reaction struct { + MessageId string `json:"message_id"` + Emoji string `json:"emoji"` + } `json:"reaction,omitempty"` +} + +type NotificationPayloadInteractionMessageSchemaType struct { + Interactive struct { + Type InteractiveNotificationTypeEnum `json:"type"` + } `json:"interactive,omitempty"` +} + +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 NotificationPayloadContactMessageSchemaType struct { + Contacts []Contact `json:"contacts"` +} + +type NotificationMessageTypeEnum string + +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 InteractiveNotificationTypeEnum string + +const ( + NotificationTypeButtonReply InteractiveNotificationTypeEnum = "button_reply" + NotificationTypeListReply InteractiveNotificationTypeEnum = "list_reply" +) + +type AdInteractionSourceTypeEnum string + +const ( + AdInteractionSourceTypeUnknown AdInteractionSourceTypeEnum = "unknown" + // Add other ad interaction source types +) + +type AdInteractionSourceMediaTypeEnum string + +const ( + AdInteractionSourceMediaTypeImage AdInteractionSourceMediaTypeEnum = "image" + AdInteractionSourceMediaTypeVideo AdInteractionSourceMediaTypeEnum = "video" + // Add other ad interaction source media types +) + +type SystemNotificationTypeEnum string + +const ( + SystemNotificationTypeUnknown SystemNotificationTypeEnum = "unknown" + // Add other system notification types +) + +type Contact struct { + WaId string `json:"wa_id"` + Profile Profile `json:"profile"` +} + +type Profile struct { + Name string `json:"name"` +} + +type WhatsappApiNotificationPayloadSchemaType struct { + Object string `json:"object"` + Entry []Entry `json:"entry"` +} + +type Entry struct { + Id string `json:"id"` + Changes []Change `json:"changes"` +} + +type Change struct { + Value Value `json:"value"` + Field string `json:"field"` +} + +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 Metadata struct { + DisplayPhoneNumber string `json:"display_phone_number"` + PhoneNumberId string `json:"phone_number_id"` +} + +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 Conversation struct { + Id string `json:"id"` + Origin Origin `json:"origin,omitempty"` +} + +type Origin struct { + Type MessageStatusCategoryEnum `json:"type"` + ExpirationTimestamp string `json:"expiration_timestamp,omitempty"` +} + +type Pricing struct { + PricingModel string `json:"pricing_model"` + Category MessageStatusCategoryEnum `json:"category"` +} + +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"` +} + +type Error struct { + // Add fields for error details +} + +type MessageStatusCategoryEnum string + +const ( + MessageStatusCategorySent MessageStatusCategoryEnum = "sent" +) + +// Add other message status categories + +type MessageStatusEnum string + +const ( + MessageStatusDelivered MessageStatusEnum = "delivered" + // Add other message statuses +) diff --git a/internal/manager/webhook_manager.go b/internal/manager/webhook_manager.go new file mode 100644 index 0000000..c31ebba --- /dev/null +++ b/internal/manager/webhook_manager.go @@ -0,0 +1,309 @@ +// Package manager provides functionality for managing webhooks. +package manager + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "os" + "os/signal" + "time" + + "github.com/labstack/echo/v4" + requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/pkg/events" + "github.com/sarthakjdev/wapi.go/utils" +) + +// WebhookManager represents a manager for handling webhooks. +type WebhookManager struct { + secret string + path string + port int + EventManager EventManger + Requester requestclient.RequestClient +} + +// WebhookManagerConfig represents the configuration options for creating a new WebhookManager. +type WebhookManagerConfig struct { + Secret string + Path string + Port int + EventManager EventManger + Requester requestclient.RequestClient +} + +// NewWebhook creates a new WebhookManager with the given options. +func NewWebhook(options *WebhookManagerConfig) *WebhookManager { + return &WebhookManager{ + secret: options.Secret, + path: options.Path, + port: options.Port, + EventManager: options.EventManager, + Requester: options.Requester, + } +} + +// createEchoHttpServer creates a new instance of Echo HTTP server. +// This function is used in case the client has not provided any custom HTTP server. +func (wh *WebhookManager) createEchoHttpServer() *echo.Echo { + e := echo.New() + return e +} + +// GetRequestHandler handles GET requests to the webhook endpoint. +func (wh *WebhookManager) GetRequestHandler(c echo.Context) error { + hubVerificationToken := c.QueryParam("hub.verify_token") + hubChallenge := c.QueryParam("hub.challenge") + fmt.Println(hubVerificationToken, hubChallenge) + if hubVerificationToken == wh.secret { + return c.String(200, hubChallenge) + } else { + return c.String(400, "invalid token") + } +} + +// PostRequestHandler handles POST requests to the webhook endpoint. +func (wh *WebhookManager) PostRequestHandler(c echo.Context) error { + body, err := io.ReadAll(c.Request().Body) + if err != nil { + fmt.Println("Error reading request body:", err) + c.String(400, "error reading request body") + } + + var payload WhatsappApiNotificationPayloadSchemaType + + if err := json.Unmarshal(body, &payload); err != nil { + fmt.Println("Error unmarshaling JSON:", err) + c.String(400, "Invalid JSON data") + } + + if err := utils.GetValidator().Struct(payload); err != nil { + fmt.Println("Error validating JSON:", err) + c.String(400, "Invalid JSON data") + } + + for _, entry := range payload.Entry { + for _, change := range entry.Changes { + for _, message := range change.Value.Messages { + switch message.Type { + case NotificationMessageTypeText: + { + wh.EventManager.Publish(TextMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeImage: + { + + wh.EventManager.Publish(ImageMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + + } + case NotificationMessageTypeVideo: + { + + wh.EventManager.Publish(AudioMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + + } + case NotificationMessageTypeDocument: + { + + wh.EventManager.Publish(DocumentMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeAudio: + { + wh.EventManager.Publish(AudioMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeLocation: + { + wh.EventManager.Publish(LocationMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeContacts: + { + wh.EventManager.Publish(ContactMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeSticker: + { + wh.EventManager.Publish(StickerMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeSystem: + { + wh.EventManager.Publish(TextMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeButton: + { + wh.EventManager.Publish(ReplyButtonInteractionEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeInteractive: + { + wh.EventManager.Publish(ListInteractionMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeReaction: + { + wh.EventManager.Publish(ReactionMessageEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeOrder: + { + wh.EventManager.Publish(OrderReceivedEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + case NotificationMessageTypeUnknown: + { + wh.EventManager.Publish(UnknownEvent, events.NewTextMessageEvent( + events.NewBaseMessageEvent( + message.Id, + "", + message.From, + message.Context.Forwarded, + wh.Requester), + message.Text.Body), + ) + } + } + + } + } + } + + c.String(200, "Message received") + + fmt.Println("Received valid payload:", payload.Entry[0].Changes[0].Value.Messages[0].Type) + + return nil +} + +// ListenToEvents starts listening to events and handles incoming requests. +func (wh *WebhookManager) ListenToEvents() { + fmt.Println("Listening to events") + server := wh.createEchoHttpServer() + server.GET(wh.path, wh.GetRequestHandler) + server.POST(wh.path, wh.PostRequestHandler) + + // Start server in a goroutine + go func() { + if err := server.Start(":8080"); err != nil { + return + } + }() + + wh.EventManager.Publish(ReadyEvent, events.NewReadyEvent()) + // Wait for an interrupt signal (e.g., Ctrl+C) + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) // Capture SIGINT (Ctrl+C) + <-quit // Wait for the signal + + // Gracefully shut down the server (optional) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := server.Shutdown(ctx); err != nil { + log.Fatal(err) // Handle shutdown errors gracefully + } +} + +// determineEventType determines the event type. +func (wh *WebhookManager) determineEventType() EventType { + return TextMessageEvent +} diff --git a/internal/request_client/request_client.go b/internal/request_client/request_client.go new file mode 100644 index 0000000..a333205 --- /dev/null +++ b/internal/request_client/request_client.go @@ -0,0 +1,64 @@ +package requestclient + +import ( + "fmt" + "io" + "net/http" + "strings" +) + +const ( + API_VERSION = "v19.0" + BASE_URL = "graph.facebook.com" + REQUEST_PROTOCOL = "https" +) + +// 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 { + 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 +} + +// 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)) + 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) + if err != nil { + fmt.Println("Error while requesting cloud api", err) + return "", err + } + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + if err != nil { + return "", err + } + + fmt.Println("Response from cloud api is", string(body)) + + return string(body), nil +} diff --git a/pkg/client/cient.go b/pkg/client/cient.go deleted file mode 100644 index 37c1b90..0000000 --- a/pkg/client/cient.go +++ /dev/null @@ -1,18 +0,0 @@ -package client - -import "github.com/sarthakjdev/wapi.go/pkg/manager" - -type Client struct { - media manager.MediaManager - message manager.MessageManager - requester requestClient -} - -func NewClient() *Client { - return &Client{ - media: *manager.NewMediaManager(), - message: *manager.NewMessageManager(), - requester: *NewRequestClient(), - } -} - diff --git a/pkg/client/client.go b/pkg/client/client.go new file mode 100644 index 0000000..1806e38 --- /dev/null +++ b/pkg/client/client.go @@ -0,0 +1,88 @@ +package wapi + +import ( + "fmt" + + "github.com/labstack/echo/v4" + "github.com/sarthakjdev/wapi.go/internal/manager" + requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/pkg/events" + "github.com/sarthakjdev/wapi.go/utils" +) + +// 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"` + WebhookSecret string `validate:"required"` + WebhookServerPort int +} + +// NewWapiClient creates a new instance of Client. +func New(configs ClientConfig) (*Client, error) { + // Validate the client configuration options + err := utils.GetValidator().Struct(configs) + if err != nil { + return nil, fmt.Errorf("error validating client config: %w", err) + } + + // Create a new request client + requester := *requestclient.NewRequestClient(configs.PhoneNumberId, configs.ApiAccessToken) + + // Create a new event manager + eventManager := *manager.NewEventManager() + + // Create a new Client instance with the provided configurations + 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 +} + +// GetPhoneNumberId returns the phone number ID associated with the client. +func (client *Client) GetPhoneNumberId() string { + return client.phoneNumberId +} + +// SetPhoneNumberId sets the phone number ID for the client. +func (client *Client) SetPhoneNumberId(phoneNumberId string) { + client.phoneNumberId = phoneNumberId +} + +// 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 +} + +// GetWebhookGetRequestHandler returns the handler function for handling GET requests to the webhook. +func (client *Client) GetWebhookGetRequestHandler() func(c echo.Context) error { + return client.webhook.GetRequestHandler +} + +// GetWebhookPostRequestHandler returns the handler function for handling POST requests to the webhook. +func (client *Client) GetWebhookPostRequestHandler() func(c echo.Context) error { + return client.webhook.PostRequestHandler +} + +// OnMessage registers a handler for a specific event type. +func (client *Client) On(eventType manager.EventType, handler func(events.BaseEvent)) { + client.webhook.EventManager.On(eventType, handler) +} diff --git a/pkg/client/request_client.go b/pkg/client/request_client.go deleted file mode 100644 index 014aa59..0000000 --- a/pkg/client/request_client.go +++ /dev/null @@ -1,14 +0,0 @@ -package client - -import "fmt" - -type requestClient struct { -} - -func NewRequestClient() *requestClient { - return &requestClient{} -} - -func (*requestClient) requestCloudApi() { - fmt.Println("Requesting cloud api") -} diff --git a/pkg/components/audio_message.go b/pkg/components/audio_message.go new file mode 100644 index 0000000..9f733f1 --- /dev/null +++ b/pkg/components/audio_message.go @@ -0,0 +1,71 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// AudioMessage represents an audio message. +type AudioMessage struct { + Id string `json:"id,omitempty"` + Link string `json:"link,omitempty"` +} + +// AudioMessageApiPayload represents the payload for an audio message API request. +type AudioMessageApiPayload struct { + BaseMessagePayload + Audio AudioMessage `json:"audio" validate:"required"` +} + +// AudioMessageConfigs is an alias for AudioMessage. +type AudioMessageConfigs = AudioMessage + +// NewAudioMessage creates a new AudioMessage object. +func NewAudioMessage(params AudioMessageConfigs) (*AudioMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + idSet := params.Id != "" + linkSet := params.Link != "" + + if idSet && linkSet { + return nil, fmt.Errorf("only one of ID or Link can be provided") + } + if !idSet && !linkSet { + return nil, fmt.Errorf("either ID or Link must be provided") + } + + return &AudioMessage{ + Id: params.Id, + Link: params.Link, + }, nil +} + +// ToJson converts the AudioMessage object to JSON. +func (audio *AudioMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := AudioMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeAudio), + Audio: *audio, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/base_message.go b/pkg/components/base_message.go new file mode 100644 index 0000000..b4b9a1a --- /dev/null +++ b/pkg/components/base_message.go @@ -0,0 +1,54 @@ +package components + +// MessageType represents the type of message. +type MessageType string + +// Constants for different message types. +const ( + MessageTypeLocation MessageType = "location" + MessageTypeAudio MessageType = "audio" + MessageTypeVideo MessageType = "video" + MessageTypeDocument MessageType = "document" + MessageTypeText MessageType = "text" + MessageTypeContact MessageType = "contacts" + MessageTypeInteractive MessageType = "interactive" + MessageTypeTemplate MessageType = "template" + MessageTypeReaction MessageType = "reaction" + MessageTypeSticker MessageType = "sticker" + MessageTypeImage MessageType = "image" +) + +// ApiCompatibleJsonConverterConfigs represents the configuration for converting to JSON. +type ApiCompatibleJsonConverterConfigs struct { + ReplyToMessageId string + SendToPhoneNumber string +} + +// Context represents the context of the message. +type Context struct { + MessageId string `json:"message_id,omitempty"` +} + +// BaseMessagePayload represents the base payload to send messages. +type BaseMessagePayload struct { + Context *Context `json:"context,omitempty"` + To string `json:"to"` + Type MessageType `json:"type"` + MessagingProduct string `json:"messaging_product"` + RecipientType string `json:"recipient_type"` +} + +// NewBaseMessagePayload creates a new instance of BaseMessagePayload. +func NewBaseMessagePayload(to string, messageType MessageType) BaseMessagePayload { + return BaseMessagePayload{ + To: to, + Type: messageType, + MessagingProduct: "whatsapp", + RecipientType: "individual", + } +} + +// BaseMessage is an interface for sending messages. +type BaseMessage interface { + ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) +} diff --git a/pkg/components/contact_message.go b/pkg/components/contact_message.go new file mode 100644 index 0000000..ec4920f --- /dev/null +++ b/pkg/components/contact_message.go @@ -0,0 +1,174 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +type AddressType string + +const ( + HomeAddress AddressType = "HOME" + WorkAddress AddressType = "WORK" +) + +type UrlType string + +const ( + HomeUrl AddressType = "HOME" + WorkUrl AddressType = "WORK" +) + +type EmailType string + +const ( + HomeEmail EmailType = "HOME" + WorkEmail EmailType = "WORK" +) + +type PhoneType string + +const ( + CellPhone PhoneType = "CELL" + MainPhone PhoneType = "MAIN" + IphonePhone PhoneType = "IPHONE" + HomePhone PhoneType = "HOME" + WorkPhone PhoneType = "WORK" +) + +type ContactAddress struct { + Street string `json:"street,omitempty"` + City string `json:"city,omitempty"` + State string `json:"state,omitempty"` + Zip string `json:"zip,omitempty"` + Country string `json:"country,omitempty"` + CountryCode string `json:"countryCode,omitempty"` + Type AddressType `json:"type" validate:"required"` +} + +type ContactName struct { + FormattedName string `json:"formatted_name" validate:"required"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + MiddleName string `json:"middle_name,omitempty"` + Suffix string `json:"suffix,omitempty"` + Prefix string `json:"prefix,omitempty"` +} + +type ContactOrg struct { + Company string `json:"company,omitempty"` + Title string `json:"title,omitempty"` + Department string `json:"department,omitempty"` +} + +type ContactEmail struct { + Email string `json:"email,omitempty"` + Type EmailType `json:"type,omitempty"` +} + +type ContactPhone struct { + Phone string `json:"phone,omitempty"` + WaId string `json:"wa_id,omitempty"` + Type PhoneType `json:"type" validate:"required"` +} + +type ContactUrl struct { + Url string `json:"url" validate:"required"` + Type UrlType `json:"type" validate:"required"` +} + +type Contact struct { + Name ContactName `json:"name" validate:"required"` + Org ContactOrg `json:"org,omitempty"` + Addresses []ContactAddress `json:"addresses,omitempty"` + Urls []ContactUrl `json:"urls,omitempty"` + Emails []ContactEmail `json:"emails,omitempty"` + Phones []ContactPhone `json:"phones,omitempty"` + Birthday string `json:"birthday,omitempty"` +} + +func NewContact(name ContactName) *Contact { + return &Contact{ + Name: name, + } +} + +func (contact *ContactMessage) AddContact(params Contact) { + contact.Contacts = append(contact.Contacts, params) +} + +func (contact *Contact) SetFirstName(firstName string) { + contact.Name.FirstName = firstName +} + +func (contact *Contact) SetLastName(lastName string) { + contact.Name.LastName = lastName +} + +func (contact *Contact) SetMiddleName(middleName string) { + contact.Name.MiddleName = middleName +} + +func (contact *Contact) SetOrg(params ContactOrg) { + contact.Org = params +} + +func (contact *Contact) AddPhone(params ContactPhone) { + contact.Phones = append(contact.Phones, params) +} + +func (contact *Contact) AddEmail(params ContactEmail) { + contact.Emails = append(contact.Emails, params) +} + +func (contact *Contact) AddUrl(params ContactUrl) { + contact.Urls = append(contact.Urls, params) +} + +// ! TODO: add regex check here in the params +func (contact *Contact) SetBirthday(params string) { + contact.Birthday = params +} + +type ContactMessage struct { + Contacts []Contact `json:"contacts" validate:"required"` +} + +type ContactMessageConfigs struct { + Name string `json:"name" validate:"required"` +} + +type ContactMessageApiPayload struct { + BaseMessagePayload + Contacts []Contact `json:"contacts" validate:"required"` +} + +func NewContactMessage(configs []Contact) (*ContactMessage, error) { + return &ContactMessage{ + Contacts: configs, + }, nil +} + +func (m *ContactMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + jsonData := ContactMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeContact), + Contacts: m.Contacts, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + return jsonToReturn, nil +} diff --git a/pkg/components/document_message.go b/pkg/components/document_message.go new file mode 100644 index 0000000..9dc771c --- /dev/null +++ b/pkg/components/document_message.go @@ -0,0 +1,57 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// DocumentMessage represents a document message. +type DocumentMessage struct { +} + +// DocumentMessageApiPayload represents the API payload for a document message. +type DocumentMessageApiPayload struct { + BaseMessagePayload + Document DocumentMessage `json:"document" validate:"required"` +} + +// DocumentMessageConfigs represents the configurations for a document message. +type DocumentMessageConfigs struct { +} + +// NewDocumentMessage creates a new DocumentMessage instance. +func NewDocumentMessage(params DocumentMessageConfigs) (*DocumentMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + return &DocumentMessage{}, nil +} + +// ToJson converts the DocumentMessage instance to JSON. +func (dm *DocumentMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := DocumentMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeDocument), + Document: *dm, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/image_message.go b/pkg/components/image_message.go new file mode 100644 index 0000000..780cb92 --- /dev/null +++ b/pkg/components/image_message.go @@ -0,0 +1,78 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// ImageMessage represents a message with an image. +type ImageMessage struct { + Id string `json:"id,omitempty"` + Link string `json:"link,omitempty"` + Caption string `json:"caption,omitempty"` +} + +// ImageMessageApiPayload represents the API payload for an image message. +type ImageMessageApiPayload struct { + BaseMessagePayload + Image ImageMessage `json:"image" validate:"required"` +} + +// ImageMessageConfigs is an alias for ImageMessage. +type ImageMessageConfigs = ImageMessage + +// SetCaption sets the caption for the image message. +func (image *ImageMessage) SetCaption(params string) { + image.Caption = params +} + +// NewImageMessage creates a new ImageMessage instance. +func NewImageMessage(params ImageMessageConfigs) (*ImageMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + idSet := params.Id != "" + linkSet := params.Link != "" + + if idSet && linkSet { + return nil, fmt.Errorf("only one of ID or Link can be provided") + } + if !idSet && !linkSet { + return nil, fmt.Errorf("either ID or Link must be provided") + } + + return &ImageMessage{ + Id: params.Id, + Link: params.Link, + Caption: params.Caption, + }, nil +} + +// ToJson converts the ImageMessage to JSON. +func (image *ImageMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := ImageMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeImage), + Image: *image, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/interactive_message.go b/pkg/components/interactive_message.go new file mode 100644 index 0000000..4cd8a0a --- /dev/null +++ b/pkg/components/interactive_message.go @@ -0,0 +1,10 @@ +package components + +type InteractiveMessageType string + +const ( + InteractiveMessageTypeButton InteractiveMessageType = "button" + InteractiveMessageTypeProduct InteractiveMessageType = "product" + InteractiveMessageTypeProductList InteractiveMessageType = "product_list" + InteractiveMessageTypeList InteractiveMessageType = "list" +) diff --git a/pkg/components/list_message.go b/pkg/components/list_message.go new file mode 100644 index 0000000..918346b --- /dev/null +++ b/pkg/components/list_message.go @@ -0,0 +1,146 @@ +// Package components provides components for creating interactive list messages. +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// listSection represents a section in the list message. +type listSection struct { + Title string `json:"title" validate:"required"` // Title of the section. + Rows []listSectionRow `json:"rows" validate:"required"` // Rows in the section. +} + +// NewListSection creates a new list section with the given title. +func NewListSection(title string) (*listSection, error) { + return &listSection{ + Title: title, + }, nil +} + +// AddRow adds a new row to the list section. +func (section *listSection) AddRow(row *listSectionRow) { + section.Rows = append(section.Rows, *row) +} + +// SetTitle sets the title of the list section. +func (section *listSection) SetTitle(title string) { + section.Title = title +} + +// listSectionRow represents a row in the list section. +type listSectionRow struct { + Id string `json:"id" validate:"required"` // ID of the row. + Description string `json:"description" validate:"required"` // Description of the row. + Title string `json:"title" validate:"required"` // Title of the row. +} + +// NewListSectionRow creates a new list section row with the given ID, title, and description. +func NewListSectionRow(id, title, description string) (*listSectionRow, error) { + return &listSectionRow{ + Id: id, + Description: description, + Title: title, + }, nil +} + +// SetTitle sets the title of the list section row. +func (row *listSectionRow) SetTitle(title string) { + row.Title = title +} + +// SetDescription sets the description of the list section row. +func (row *listSectionRow) SetDescription(description string) { + row.Description = description +} + +// SetId sets the ID of the list section row. +func (row *listSectionRow) SetId(id string) { + row.Id = id +} + +// listMessageAction represents the action of the list message. +type listMessageAction struct { + ButtonText string `json:"button" validate:"required"` // Text of the button. + Sections []listSection `json:"sections" validate:"required"` // Sections in the list message. +} + +// ListMessageBody represents the body of the list message. +type ListMessageBody struct { + Text string `json:"text" validate:"required"` // Text of the body. +} + +// listMessage represents an interactive list message. +type listMessage struct { + Type InteractiveMessageType `json:"type" validate:"required"` // Type of the message. + Action listMessageAction `json:"action" validate:"required"` // Action of the message. + Body ListMessageBody `json:"body,omitempty"` // Body of the message. +} + +// ListMessageParams represents the parameters for creating a list message. +type ListMessageParams struct { + ButtonText string `json:"-" validate:"required"` // Text of the button. + BodyText string `json:"-" validate:"required"` // Text of the body. +} + +// NewListMessage creates a new list message with the given parameters. +func NewListMessage(params ListMessageParams) (*listMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + return &listMessage{ + Type: InteractiveMessageTypeList, + Body: ListMessageBody{ + Text: params.BodyText, + }, + Action: listMessageAction{ + ButtonText: params.ButtonText, + }, + }, nil +} + +// ListMessageApiPayload represents the API payload for the list message. +type ListMessageApiPayload struct { + BaseMessagePayload + Interactive listMessage `json:"interactive" validate:"required"` // Interactive message. +} + +// AddSection adds a new section to the list message. +func (m *listMessage) AddSection(section *listSection) { + m.Action.Sections = append(m.Action.Sections, *section) +} + +// SetBodyText sets the body text of the list message. +func (m *listMessage) SetBodyText(section *listSection) { + m.Body.Text = section.Title +} + +// ToJson converts the list message to JSON. +func (m *listMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := ListMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeInteractive), + Interactive: *m, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/location_message.go b/pkg/components/location_message.go new file mode 100644 index 0000000..6accc5d --- /dev/null +++ b/pkg/components/location_message.go @@ -0,0 +1,66 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// LocationMessage represents a location message with latitude, longitude, address, and name. +type LocationMessage struct { + Latitude float64 `json:"latitude" validate:"required"` // Latitude of the location + Longitude float64 `json:"longitude" validate:"required"` // Longitude of the location + Address string `json:"address,omitempty"` // Address of the location (optional) + Name string `json:"name,omitempty"` // Name of the location (optional) +} + +// LocationMessageApiPayload represents the API payload for a location message. +type LocationMessageApiPayload struct { + BaseMessagePayload + Location LocationMessage `json:"location" validate:"required"` // Location message +} + +// NewLocationMessage creates a new LocationMessage with the given latitude and longitude. +func NewLocationMessage(latitude float64, longitude float64) (*LocationMessage, error) { + return &LocationMessage{ + Latitude: latitude, + Longitude: longitude, + }, nil +} + +// SetAddress sets the address of the location. +func (location *LocationMessage) SetAddress(params string) { + location.Address = params +} + +// SetName sets the name of the location. +func (location *LocationMessage) SetName(params string) { + location.Name = params +} + +// ToJson converts the LocationMessage to JSON with the given configurations. +func (location *LocationMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := LocationMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeLocation), + Location: *location, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/order.go b/pkg/components/order.go new file mode 100644 index 0000000..cbbdf1b --- /dev/null +++ b/pkg/components/order.go @@ -0,0 +1,16 @@ +package components + +// Order represents an order in the system. +type Order struct { + CatalogID string `json:"catalog_id"` // CatalogID is the ID of the catalog associated with the order. + ProductItems []ProductItem `json:"product_items"` // ProductItems is a list of product items in the order. + Text string `json:"text"` // Text is an additional text associated with the order. +} + +// ProductItem represents a product item in an order. +type ProductItem struct { + Currency string `json:"currency"` // Currency is the currency of the product item. + ItemPrice string `json:"item_price"` // ItemPrice is the price of the product item. + ProductRetailerID string `json:"product_retailer_id"` // ProductRetailerID is the ID of the retailer associated with the product item. + Quantity string `json:"quantity"` // Quantity is the quantity of the product item. +} diff --git a/pkg/components/product_list_message.go b/pkg/components/product_list_message.go new file mode 100644 index 0000000..3aefb5a --- /dev/null +++ b/pkg/components/product_list_message.go @@ -0,0 +1,57 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// ProductListMessage represents a product list message. +type ProductListMessage struct { +} + +// ProductListMessageParams represents the parameters for creating a product list message. +type ProductListMessageParams struct { +} + +// ProductListMessageApiPayload represents the API payload for a product list message. +type ProductListMessageApiPayload struct { + BaseMessagePayload + Interactive ProductListMessage `json:"interactive" validate:"required"` +} + +// NewProductListMessage creates a new product list message. +func NewProductListMessage(params ProductListMessageParams) (*ProductListMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + return &ProductListMessage{}, nil +} + +// ToJson converts the product list message to JSON. +func (m *ProductListMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := ProductListMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeInteractive), + Interactive: *m, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/product_message.go b/pkg/components/product_message.go new file mode 100644 index 0000000..0d98e54 --- /dev/null +++ b/pkg/components/product_message.go @@ -0,0 +1,56 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// ProductMessage represents a product message. +type ProductMessage struct { +} + +// ProductMessageParams represents the parameters for creating a product message. +type ProductMessageParams struct { +} + +// ProductMessageApiPayload represents the API payload for a product message. +type ProductMessageApiPayload struct { + BaseMessagePayload + Interactive ProductMessage `json:"interactive" validate:"required"` +} + +// NewProductMessage creates a new product message with the given parameters. +func NewProductMessage(params ProductMessageParams) (*ProductMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + return &ProductMessage{}, nil +} + +// ToJson converts the product message to JSON with the given configurations. +func (m *ProductMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := ProductMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeInteractive), + Interactive: *m, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/quick_reply_button_message.go b/pkg/components/quick_reply_button_message.go new file mode 100644 index 0000000..c2757fd --- /dev/null +++ b/pkg/components/quick_reply_button_message.go @@ -0,0 +1,103 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// quickReplyButtonMessageButtonReply represents the reply structure of a quick reply button. +type quickReplyButtonMessageButtonReply struct { + Title string `json:"title" validate:"required"` // Title of the quick reply button. + Id string `json:"id" validate:"required"` // ID of the quick reply button. +} + +// quickReplyButtonMessageButton represents a quick reply button. +type quickReplyButtonMessageButton struct { + Type string `json:"type" validate:"required"` // Type of the quick reply button. + Reply quickReplyButtonMessageButtonReply `json:"reply" validate:"required"` // Reply structure of the quick reply button. +} + +// NewQuickReplyButton creates a new quick reply button with the given ID and title. +func NewQuickReplyButton(id, title string) (*quickReplyButtonMessageButton, error) { + return &quickReplyButtonMessageButton{ + Type: "reply", + Reply: quickReplyButtonMessageButtonReply{ + Title: title, + Id: id, + }, + }, nil +} + +// QuickReplyButtonMessageAction represents the action of a quick reply button message. +type QuickReplyButtonMessageAction struct { + Buttons []quickReplyButtonMessageButton `json:"buttons" validate:"required"` // List of quick reply buttons. +} + +// QuickReplyButtonMessageBody represents the body of a quick reply button message. +type QuickReplyButtonMessageBody struct { + Text string `json:"text" validate:"required"` // Text of the quick reply button message. +} + +// QuickReplyButtonMessage represents a quick reply button message. +type QuickReplyButtonMessage struct { + Type InteractiveMessageType `json:"type" validate:"required"` // Type of the quick reply button message. + Body QuickReplyButtonMessageBody `json:"body" validate:"required"` // Body of the quick reply button message. + Action QuickReplyButtonMessageAction `json:"action" validate:"required"` // Action of the quick reply button message. +} + +// QuickReplyButtonMessageApiPayload represents the API payload for a quick reply button message. +type QuickReplyButtonMessageApiPayload struct { + BaseMessagePayload + Interactive QuickReplyButtonMessage `json:"interactive" validate:"required"` // Interactive part of the API payload. +} + +// NewQuickReplyButtonMessage creates a new quick reply button message with the given body text. +func NewQuickReplyButtonMessage(bodyText string) (*QuickReplyButtonMessage, error) { + return &QuickReplyButtonMessage{ + Type: InteractiveMessageTypeButton, + Body: QuickReplyButtonMessageBody{ + Text: bodyText, + }, + Action: QuickReplyButtonMessageAction{ + Buttons: []quickReplyButtonMessageButton{}, + }, + }, nil +} + +func (m *QuickReplyButtonMessage) AddButton(id, title string) error { + button, err := NewQuickReplyButton(id, title) + if err != nil { + return fmt.Errorf("error creating quick reply button: %v", err) + } + m.Action.Buttons = append(m.Action.Buttons, *button) + + return nil +} + +// ToJson converts the quick reply button message to JSON. +func (m *QuickReplyButtonMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := QuickReplyButtonMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeInteractive), + Interactive: *m, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/reaction_message.go b/pkg/components/reaction_message.go new file mode 100644 index 0000000..0e88c57 --- /dev/null +++ b/pkg/components/reaction_message.go @@ -0,0 +1,61 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// ReactionMessage represents a reaction to a message. +type ReactionMessage struct { + MessageId string `json:"message_id" validate:"required"` // The ID of the message to react to. + Emoji string `json:"emoji" validate:"required"` // The emoji representing the reaction. +} + +// ReactionMessageParams is an alias for ReactionMessage. +type ReactionMessageParams = ReactionMessage + +// ReactionMessageApiPayload represents the API payload for a reaction message. +type ReactionMessageApiPayload struct { + BaseMessagePayload + Reaction ReactionMessage `json:"reaction" validate:"required"` // The reaction message. +} + +// NewReactionMessage creates a new ReactionMessage instance. +func NewReactionMessage(params ReactionMessageParams) (*ReactionMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + return &ReactionMessage{ + MessageId: params.MessageId, + Emoji: params.Emoji, + }, nil +} + +// ToJson converts the ReactionMessage to JSON. +func (reaction *ReactionMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := ReactionMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeReaction), + Reaction: *reaction, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/sticker_message.go b/pkg/components/sticker_message.go new file mode 100644 index 0000000..9b9ed03 --- /dev/null +++ b/pkg/components/sticker_message.go @@ -0,0 +1,73 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// StickerMessage represents a sticker message. +type StickerMessage struct { + Id string `json:"id,omitempty"` + Link string `json:"link,omitempty"` +} + +// StickerMessageApiPayload represents the API payload for a sticker message. +type StickerMessageApiPayload struct { + BaseMessagePayload + Sticker StickerMessage `json:"sticker" validate:"required"` +} + +// StickerMessageConfigs represents the configurations for a sticker message. +type StickerMessageConfigs struct { + Id string `json:"id,omitempty"` + Link string `json:"link,omitempty"` +} + +// NewStickerMessage creates a new sticker message based on the provided configurations. +func NewStickerMessage(params *StickerMessageConfigs) (*StickerMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + idSet := params.Id != "" + linkSet := params.Link != "" + + if idSet && linkSet { + return nil, fmt.Errorf("only one of ID or Link can be provided") + } + if !idSet && !linkSet { + return nil, fmt.Errorf("either ID or Link must be provided") + } + + return &StickerMessage{ + Id: params.Id, + Link: params.Link, + }, nil +} + +// ToJson converts the sticker message to JSON based on the provided configurations. +func (sticker *StickerMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := StickerMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeSticker), + Sticker: *sticker, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/template_message.go b/pkg/components/template_message.go new file mode 100644 index 0000000..293c96a --- /dev/null +++ b/pkg/components/template_message.go @@ -0,0 +1,16 @@ +package components + +// TemplateMessage represents a template message. +type TemplateMessage struct { +} + +// TemplateMessageApiPayload represents the API payload for a template message. +type TemplateMessageApiPayload struct { + BaseMessagePayload + Template TemplateMessage `json:"template" validate:"required"` +} + +// NewTemplateMessage creates a new instance of TemplateMessage. +func NewTemplateMessage() (*TemplateMessage, error) { + return &TemplateMessage{}, nil +} diff --git a/pkg/components/text_message.go b/pkg/components/text_message.go new file mode 100644 index 0000000..b8913d3 --- /dev/null +++ b/pkg/components/text_message.go @@ -0,0 +1,78 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// textMessage represents a text message. +type textMessage struct { + Text string // The text content of the message. + AllowPreview bool // Whether to allow preview of the message. +} + +// TextMessageConfigs represents the configuration options for a text message. +type TextMessageConfigs struct { + Text string `json:"text" validate:"required"` // The text content of the message. + AllowPreview bool `json:"allowPreview,omitempty"` // Whether to allow preview of the message. +} + +// TextMessageApiPayloadText represents the text payload for the WhatsApp API. +type TextMessageApiPayloadText struct { + Body string `json:"body" validate:"required"` // The text content of the message. + AllowPreview bool `json:"preview_url,omitempty"` // Whether to allow preview of the message. +} + +// SetText sets the text content of the message. +func (m *textMessage) SetText(text string) { + m.Text = text +} + +// TextMessageApiPayload represents the payload for the WhatsApp API. +type TextMessageApiPayload struct { + BaseMessagePayload `json:",inline"` + Text TextMessageApiPayloadText `json:"text" validate:"required"` // The text content of the message. +} + +// NewTextMessage creates a new text message with the given configurations. +func NewTextMessage(configs TextMessageConfigs) (*textMessage, error) { + err := utils.GetValidator().Struct(configs) + if err != nil { + return nil, fmt.Errorf("error validating text message config: %v", err) + } + return &textMessage{ + Text: configs.Text, + AllowPreview: configs.AllowPreview, + }, nil +} + +// ToJson converts the text message struct to WhatsApp API compatible JSON. +func (m *textMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := TextMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeText), + Text: TextMessageApiPayloadText{ + Body: m.Text, + AllowPreview: m.AllowPreview, + }, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/components/video_message.go b/pkg/components/video_message.go new file mode 100644 index 0000000..9278ef4 --- /dev/null +++ b/pkg/components/video_message.go @@ -0,0 +1,77 @@ +package components + +import ( + "encoding/json" + "fmt" + + "github.com/sarthakjdev/wapi.go/utils" +) + +// VideoMessage represents a video message. +type VideoMessage struct { + Id string `json:"id,omitempty"` + Link string `json:"link,omitempty"` + Caption string `json:"caption,omitempty"` +} + +// VideoMessageApiPayload represents the API payload for a video message. +type VideoMessageApiPayload struct { + BaseMessagePayload + Video VideoMessage `json:"video" validate:"required"` +} + +// VideoMessageConfigs is an alias for VideoMessage. +type VideoMessageConfigs = VideoMessage + +// NewVideoMessage creates a new VideoMessage instance. +func NewVideoMessage(params VideoMessageConfigs) (*VideoMessage, error) { + if err := utils.GetValidator().Struct(params); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + idSet := params.Id != "" + linkSet := params.Link != "" + + if idSet && linkSet { + return nil, fmt.Errorf("only one of ID or Link can be provided") + } + if !idSet && !linkSet { + return nil, fmt.Errorf("either ID or Link must be provided") + } + + return &VideoMessage{ + Id: params.Id, + Link: params.Link, + }, nil +} + +// SetCaption sets the caption for the video message. +func (video *VideoMessage) SetCaption(params string) { + video.Caption = params +} + +// ToJson converts the video message to JSON. +func (video *VideoMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) { + if err := utils.GetValidator().Struct(configs); err != nil { + return nil, fmt.Errorf("error validating configs: %v", err) + } + + jsonData := VideoMessageApiPayload{ + BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeVideo), + Video: *video, + } + + if configs.ReplyToMessageId != "" { + jsonData.Context = &Context{ + MessageId: configs.ReplyToMessageId, + } + } + + jsonToReturn, err := json.Marshal(jsonData) + + if err != nil { + return nil, fmt.Errorf("error marshalling json: %v", err) + } + + return jsonToReturn, nil +} diff --git a/pkg/events/ad_interaction_event.go b/pkg/events/ad_interaction_event.go new file mode 100644 index 0000000..9944156 --- /dev/null +++ b/pkg/events/ad_interaction_event.go @@ -0,0 +1,50 @@ +package events + +// AdInteractionSourceType represents the source type of an ad interaction. +type AdInteractionSourceType string + +const ( + // AdInteractionSourceTypePost indicates that the ad interaction is from a post. + AdInteractionSourceTypePost AdInteractionSourceType = "post" + // AdInteractionSourceTypeAd indicates that the ad interaction is from an ad. + AdInteractionSourceTypeAd AdInteractionSourceType = "ad" +) + +// AdInteractionSourceMediaType represents the media type of an ad interaction source. +type AdInteractionSourceMediaType string + +const ( + // AdInteractionSourceMediaTypeImage indicates that the ad interaction source is an image. + AdInteractionSourceMediaTypeImage AdInteractionSourceMediaType = "image" + // AdInteractionSourceMediaTypeVideo indicates that the ad interaction source is a video. + AdInteractionSourceMediaTypeVideo AdInteractionSourceMediaType = "video" +) + +// AdSource represents the source of an ad. +type AdSource struct { + Url string `json:"url"` + Id string `json:"id"` + Type AdInteractionSourceType `json:"type"` + Title string `json:"title"` + Description string `json:"description"` + MediaUrl string `json:"mediaUrl"` + MediaType AdInteractionSourceMediaType `json:"mediaType"` + ThumbnailUrl string `json:"thumbnailUrl"` + CtwaClid string `json:"ctwaClid"` +} + +// AdInteractionEvent represents an ad interaction event. +type AdInteractionEvent struct { + BaseMessageEvent `json:",inline"` + AdSource AdSource `json:"adSource"` + Text string `json:"text"` +} + +// NewAdInteractionEvent creates a new instance of AdInteractionEvent. +func NewAdInteractionEvent(baseMessageEvent BaseMessageEvent, adSource AdSource, text string) *AdInteractionEvent { + return &AdInteractionEvent{ + BaseMessageEvent: baseMessageEvent, + AdSource: adSource, + Text: text, + } +} diff --git a/pkg/events/audio_message_event.go b/pkg/events/audio_message_event.go new file mode 100644 index 0000000..71ed1f5 --- /dev/null +++ b/pkg/events/audio_message_event.go @@ -0,0 +1,24 @@ +package events + +import ( + "github.com/sarthakjdev/wapi.go/pkg/components" +) + +// AudioMessageEvent represents an event for an audio message. +type AudioMessageEvent struct { + BaseMediaMessageEvent `json:",inline"` + Audio components.AudioMessage `json:"audio"` +} + +// NewAudioMessageEvent creates a new AudioMessageEvent instance. +func NewAudioMessageEvent(baseMessageEvent BaseMessageEvent, mediaId string, audio components.AudioMessage, mime_type, sha256, mimeType string) *AudioMessageEvent { + return &AudioMessageEvent{ + BaseMediaMessageEvent: BaseMediaMessageEvent{ + BaseMessageEvent: baseMessageEvent, + MediaId: mediaId, + Sha256: sha256, + MimeType: mimeType, + }, + Audio: audio, + } +} diff --git a/pkg/events/base_event.go b/pkg/events/base_event.go index 2cd9bb9..fd6b60e 100644 --- a/pkg/events/base_event.go +++ b/pkg/events/base_event.go @@ -1,20 +1,98 @@ package events -type BaseEvent struct { +import ( + requestclient "github.com/sarthakjdev/wapi.go/internal/request_client" + "github.com/sarthakjdev/wapi.go/pkg/components" +) + +type MessageContext struct { + From string `json:"from"` } -type BaseMessageEvent struct { +type BaseEvent interface { + GetEventType() string +} + +type BaseMessageEventInterface interface { BaseEvent + Reply() (string, error) + React() (string, error) } -type BaseSystemEvent struct { +type BaseSystemEventInterface interface { BaseEvent } -func (*BaseMessageEvent) Reply() { +type BaseMessageEvent struct { + requester requestclient.RequestClient + MessageId string `json:"message_id"` + Context MessageContext `json:"context"` + Timestamp string `json:"timestamp"` + IsForwarded bool `json:"is_forwarded"` +} + +func NewBaseMessageEvent(messageId string, timestamp string, from string, isForwarded bool, requester requestclient.RequestClient) BaseMessageEvent { + return BaseMessageEvent{ + MessageId: messageId, + Context: MessageContext{ + From: from, + }, + requester: requester, + Timestamp: timestamp, + IsForwarded: isForwarded, + } +} + +func (bme BaseMessageEvent) GetEventType() string { + return "message" +} + +// 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, + }) + + if err != nil { + return "", err + } + + baseMessageEvent.requester.RequestCloudApi(requestclient.RequestCloudApiParams{ + Body: string(body), + Path: "/" + baseMessageEvent.requester.PhoneNumberId + "/messages", + }) + + return "", nil } -func (*BaseMessageEvent) React() { +// React to the message +func (baseMessageEvent *BaseMessageEvent) React(emoji string) (string, error) { + reactionMessage, err := components.NewReactionMessage(components.ReactionMessageParams{ + Emoji: emoji, + MessageId: baseMessageEvent.MessageId, + }) + if err != nil { + return "", err + } + baseMessageEvent.Reply(reactionMessage) + return "", nil +} + +// BaseMediaMessageEvent represents a base media message event which contains media information. +type BaseMediaMessageEvent struct { + BaseMessageEvent `json:",inline"` + MediaId string `json:"media_id"` + MimeType string `json:"mime_type"` + Sha256 string `json:"sha256"` +} + +type BaseSystemEvent struct { + Timestamp string `json:"timestamp"` +} +func (bme BaseSystemEvent) GetEventType() string { + return "system" } diff --git a/pkg/events/contacts_message_event.go b/pkg/events/contacts_message_event.go new file mode 100644 index 0000000..c096833 --- /dev/null +++ b/pkg/events/contacts_message_event.go @@ -0,0 +1,17 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// ContactsMessageEvent represents an event that occurs when a message with contacts is received. +type ContactsMessageEvent struct { + BaseMessageEvent `json:",inline"` + Contacts components.ContactMessage `json:"contacts"` +} + +// NewContactsMessageEvent creates a new ContactsMessageEvent instance. +func NewContactsMessageEvent(baseMessageEvent BaseMessageEvent, contacts components.ContactMessage) *ContactsMessageEvent { + return &ContactsMessageEvent{ + BaseMessageEvent: baseMessageEvent, + Contacts: contacts, + } +} diff --git a/pkg/events/customer_identity_changed_event.go b/pkg/events/customer_identity_changed_event.go new file mode 100644 index 0000000..f93b1bc --- /dev/null +++ b/pkg/events/customer_identity_changed_event.go @@ -0,0 +1,8 @@ +package events + +type CustomerIdentityChangedEvent struct { + BaseSystemEvent `json:",inline"` + Acknowledged string `json:"acknowledged"` + CreationTimestamp string `json:"creationTime"` + Hash string `json:"hash"` +} diff --git a/pkg/events/customer_number_change_event.go b/pkg/events/customer_number_change_event.go new file mode 100644 index 0000000..43fc931 --- /dev/null +++ b/pkg/events/customer_number_change_event.go @@ -0,0 +1,8 @@ +package events + +type CustomerNumberChangedEvent struct { + BaseMessageEvent `json:",inline"` + ChangeDescription string `json:"changeDescription"` + NewWaId string `json:"newWaId"` + OldWaId string `json:"oldWaId"` +} diff --git a/pkg/events/document_message_event.go b/pkg/events/document_message_event.go new file mode 100644 index 0000000..67848ad --- /dev/null +++ b/pkg/events/document_message_event.go @@ -0,0 +1,22 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// DocumentMessageEvent represents an event that occurs when a document message is received. +type DocumentMessageEvent struct { + BaseMediaMessageEvent + Document components.DocumentMessage +} + +// NewDocumentMessageEvent creates a new DocumentMessageEvent instance. +func NewDocumentMessageEvent(baseMessageEvent BaseMessageEvent, document components.DocumentMessage, mediaId, sha256, mimeType string) *DocumentMessageEvent { + return &DocumentMessageEvent{ + BaseMediaMessageEvent: BaseMediaMessageEvent{ + MediaId: mediaId, + Sha256: sha256, + MimeType: mimeType, + BaseMessageEvent: baseMessageEvent, + }, + Document: document, + } +} diff --git a/pkg/events/image_message_event.go b/pkg/events/image_message_event.go new file mode 100644 index 0000000..276bfd6 --- /dev/null +++ b/pkg/events/image_message_event.go @@ -0,0 +1,22 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// ImageMessageEvent represents an event for an image message. +type ImageMessageEvent struct { + BaseMediaMessageEvent `json:",inline"` + Image components.ImageMessage `json:"image"` +} + +// NewImageMessageEvent creates a new ImageMessageEvent instance. +func NewImageMessageEvent(baseMessageEvent BaseMessageEvent, image components.ImageMessage, mimeType, sha256, mediaId string) *ImageMessageEvent { + return &ImageMessageEvent{ + BaseMediaMessageEvent: BaseMediaMessageEvent{ + MediaId: mediaId, + Sha256: sha256, + MimeType: mimeType, + BaseMessageEvent: baseMessageEvent, + }, + Image: image, + } +} diff --git a/pkg/events/list_interaction_event.go b/pkg/events/list_interaction_event.go new file mode 100644 index 0000000..e70422f --- /dev/null +++ b/pkg/events/list_interaction_event.go @@ -0,0 +1,19 @@ +package events + +// ListInteractionEvent represents an interaction event related to a list. +type ListInteractionEvent struct { + BaseMessageEvent `json:",inline"` + Title string `json:"title"` + ListId string `json:"list_id"` + Description string `json:"description"` +} + +// NewListInteractionEvent creates a new ListInteractionEvent instance. +func NewListInteractionEvent(baseMessageEvent BaseMessageEvent, title, listId, description string) *ListInteractionEvent { + return &ListInteractionEvent{ + BaseMessageEvent: baseMessageEvent, + Title: title, + ListId: listId, + Description: description, + } +} diff --git a/pkg/events/list_message_event.go b/pkg/events/list_message_event.go deleted file mode 100644 index 46cf52a..0000000 --- a/pkg/events/list_message_event.go +++ /dev/null @@ -1,5 +0,0 @@ -package events - -type ListMessageEvent struct { - BaseMessageEvent -} diff --git a/pkg/events/location_message_event.go b/pkg/events/location_message_event.go new file mode 100644 index 0000000..4697f7f --- /dev/null +++ b/pkg/events/location_message_event.go @@ -0,0 +1,17 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// LocationMessageEvent represents an event that contains a location message. +type LocationMessageEvent struct { + BaseMessageEvent `json:",inline"` + Location components.LocationMessage `json:"location"` +} + +// NewLocationMessageEvent creates a new LocationMessageEvent instance. +func NewLocationMessageEvent(baseMessageEvent BaseMessageEvent, location components.LocationMessage) *LocationMessageEvent { + return &LocationMessageEvent{ + BaseMessageEvent: baseMessageEvent, + Location: location, + } +} diff --git a/pkg/events/message_delivered_event.go b/pkg/events/message_delivered_event.go new file mode 100644 index 0000000..444bf49 --- /dev/null +++ b/pkg/events/message_delivered_event.go @@ -0,0 +1,17 @@ +package events + +// MessageDeliveredEvent represents an event related to an undelivered message. +type MessageDeliveredEvent struct { + BaseSystemEvent `json:",inline"` + MessageId string `json:"messageId"` + SentTo string `json:"sentTo"` +} + +// MessageDeliveredEvent creates a new instance of MessageUndeliveredEvent. +func NewMessageDeliveredEvent(baseSystemEvent BaseSystemEvent, messageId, sendTo string) *MessageDeliveredEvent { + return &MessageDeliveredEvent{ + BaseSystemEvent: baseSystemEvent, + MessageId: messageId, + SentTo: sendTo, + } +} diff --git a/pkg/events/message_failed_event.go b/pkg/events/message_failed_event.go new file mode 100644 index 0000000..83a2b6b --- /dev/null +++ b/pkg/events/message_failed_event.go @@ -0,0 +1,18 @@ +package events + +type MessageFailedEvent struct { + BaseSystemEvent `json:",inline"` + MessageId string `json:"messageId"` + SentTo string `json:"sentTo"` + FailReason string `json:"failReason"` +} + +func NewMessageFailedEvent(baseSystemEvent BaseSystemEvent, messageId, sendTo, failReason string) *MessageFailedEvent { + return &MessageFailedEvent{ + BaseSystemEvent: baseSystemEvent, + MessageId: messageId, + SentTo: sendTo, + FailReason: failReason, + } + +} diff --git a/pkg/events/message_read_event.go b/pkg/events/message_read_event.go new file mode 100644 index 0000000..849ac37 --- /dev/null +++ b/pkg/events/message_read_event.go @@ -0,0 +1,17 @@ +package events + +// MessageReadEvent represents an event indicating that a message has been read. +type MessageReadEvent struct { + BaseSystemEvent `json:",inline"` + MessageId string `json:"messageId"` + SentTo string `json:"sentTo"` +} + +// NewMessageReadEvent creates a new instance of MessageReadEvent. +func NewMessageReadEvent(baseSystemEvent BaseSystemEvent, messageId, sendTo string) *MessageReadEvent { + return &MessageReadEvent{ + BaseSystemEvent: baseSystemEvent, + MessageId: messageId, + SentTo: sendTo, + } +} diff --git a/pkg/events/message_sent_event.go b/pkg/events/message_sent_event.go new file mode 100644 index 0000000..2833d1b --- /dev/null +++ b/pkg/events/message_sent_event.go @@ -0,0 +1,17 @@ +package events + +// MessageSentEvent represents an event indicating that a message has been sent. +type MessageSentEvent struct { + BaseSystemEvent `json:",inline"` + MessageId string `json:"messageId"` + SentTo string `json:"sentTo"` +} + +// NewMessageSentEvent creates a new instance of MessageSentEvent. +func NewMessageSentEvent(baseSystemEvent BaseSystemEvent, messageId, sendTo string) *MessageSentEvent { + return &MessageSentEvent{ + BaseSystemEvent: baseSystemEvent, + MessageId: messageId, + SentTo: sendTo, + } +} diff --git a/pkg/events/message_undelivered_event.go b/pkg/events/message_undelivered_event.go new file mode 100644 index 0000000..0ecdca7 --- /dev/null +++ b/pkg/events/message_undelivered_event.go @@ -0,0 +1,17 @@ +package events + +// MessageUndeliveredEvent represents an event related to an undelivered message. +type MessageUndeliveredEvent struct { + BaseSystemEvent `json:",inline"` + MessageId string `json:"messageId"` + SentTo string `json:"sentTo"` +} + +// NewMessageUndeliveredEvent creates a new instance of MessageUndeliveredEvent. +func NewMessageUndeliveredEvent(baseSystemEvent BaseSystemEvent, messageId, sendTo string) *MessageUndeliveredEvent { + return &MessageUndeliveredEvent{ + BaseSystemEvent: baseSystemEvent, + MessageId: messageId, + SentTo: sendTo, + } +} diff --git a/pkg/events/order_event.go b/pkg/events/order_event.go new file mode 100644 index 0000000..7b28f9a --- /dev/null +++ b/pkg/events/order_event.go @@ -0,0 +1,17 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// OrderEvent represents an event related to an order. +type OrderEvent struct { + BaseMessageEvent `json:",inline"` + Order components.Order `json:"order"` +} + +// NewOrderEvent creates a new OrderEvent instance. +func NewOrderEvent(baseMessageEvent BaseMessageEvent, order components.Order) *OrderEvent { + return &OrderEvent{ + BaseMessageEvent: baseMessageEvent, + Order: order, + } +} diff --git a/pkg/events/product_enquiry_event.go b/pkg/events/product_enquiry_event.go new file mode 100644 index 0000000..b9c7659 --- /dev/null +++ b/pkg/events/product_enquiry_event.go @@ -0,0 +1,19 @@ +package events + +// ProductInquiryEvent represents an event related to a product inquiry. +type ProductInquiryEvent struct { + BaseMessageEvent `json:",inline"` + ProductId string `json:"productId"` + CatalogId string `json:"catalogId"` + Text string `json:"text"` +} + +// NewProductInquiryEvent creates a new instance of ProductInquiryEvent. +func NewProductInquiryEvent(baseMessageEvent BaseMessageEvent, productId, catalogId, text string) *ProductInquiryEvent { + return &ProductInquiryEvent{ + BaseMessageEvent: baseMessageEvent, + ProductId: productId, + CatalogId: catalogId, + Text: text, + } +} diff --git a/pkg/events/quick_reply_button_interaction_event.go b/pkg/events/quick_reply_button_interaction_event.go new file mode 100644 index 0000000..994f8d9 --- /dev/null +++ b/pkg/events/quick_reply_button_interaction_event.go @@ -0,0 +1,17 @@ +package events + +// QuickReplyButtonInteractionEvent represents an event triggered when a user interacts with a quick reply button. +type QuickReplyButtonInteractionEvent struct { + BaseMessageEvent `json:",inline"` + ButtonText string `json:"button_text"` + ButtonPayload string `json:"button_payload"` +} + +// NewQuickReplyButtonInteractionEvent creates a new instance of QuickReplyButtonInteractionEvent. +func NewQuickReplyButtonInteractionEvent(baseMessageEvent BaseMessageEvent, buttonText, buttonPayload string) *QuickReplyButtonInteractionEvent { + return &QuickReplyButtonInteractionEvent{ + BaseMessageEvent: baseMessageEvent, + ButtonText: buttonText, + ButtonPayload: buttonPayload, + } +} diff --git a/pkg/events/reaction_event.go b/pkg/events/reaction_event.go new file mode 100644 index 0000000..de5ec89 --- /dev/null +++ b/pkg/events/reaction_event.go @@ -0,0 +1,17 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// ReactionMessageEvent represents an event that occurs when a reaction is added to a message. +type ReactionMessageEvent struct { + BaseMessageEvent `json:",inline"` + Reaction components.ReactionMessage +} + +// NewReactionMessageEvent creates a new ReactionMessageEvent instance. +func NewReactionMessageEvent(baseMessageEvent BaseMessageEvent, reaction components.ReactionMessage) *ReactionMessageEvent { + return &ReactionMessageEvent{ + BaseMessageEvent: baseMessageEvent, + Reaction: reaction, + } +} diff --git a/pkg/events/ready_event.go b/pkg/events/ready_event.go new file mode 100644 index 0000000..feb97f1 --- /dev/null +++ b/pkg/events/ready_event.go @@ -0,0 +1,11 @@ +package events + +// ReadyEvent represents an event that is triggered when the system is ready. +type ReadyEvent struct { + BaseSystemEvent `json:",inline"` +} + +// NewReadyEvent creates a new instance of ReadyEvent. +func NewReadyEvent() *ReadyEvent { + return &ReadyEvent{} +} diff --git a/pkg/events/reply_button_interaction_event.go b/pkg/events/reply_button_interaction_event.go new file mode 100644 index 0000000..10e9ee3 --- /dev/null +++ b/pkg/events/reply_button_interaction_event.go @@ -0,0 +1,17 @@ +package events + +// ReplyButtonInteractionEvent represents an interaction event triggered by a reply button. +type ReplyButtonInteractionEvent struct { + BaseMessageEvent `json:",inline"` + Title string `json:"title"` + ButtonId string `json:"button_id"` +} + +// NewReplyButtonInteractionEvent creates a new instance of ReplyButtonInteractionEvent. +func NewReplyButtonInteractionEvent(baseMessageEvent BaseMessageEvent, title, buttonId string) *ReplyButtonInteractionEvent { + return &ReplyButtonInteractionEvent{ + BaseMessageEvent: baseMessageEvent, + Title: title, + ButtonId: buttonId, + } +} diff --git a/pkg/events/sticker_message_event.go b/pkg/events/sticker_message_event.go new file mode 100644 index 0000000..678da6b --- /dev/null +++ b/pkg/events/sticker_message_event.go @@ -0,0 +1,22 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// StickerMessageEvent represents an event for a sticker message. +type StickerMessageEvent struct { + BaseMediaMessageEvent `json:",inline"` + Sticker components.StickerMessage +} + +// NewStickerMessageEvent creates a new StickerMessageEvent instance. +func NewStickerMessageEvent(baseMessageEvent BaseMessageEvent, sticker components.StickerMessage, mediaId, sha256, mimeType string) *StickerMessageEvent { + return &StickerMessageEvent{ + BaseMediaMessageEvent: BaseMediaMessageEvent{ + MediaId: mediaId, + Sha256: sha256, + MimeType: mimeType, + BaseMessageEvent: baseMessageEvent, + }, + Sticker: sticker, + } +} diff --git a/pkg/events/text_message_event.go b/pkg/events/text_message_event.go index 42771ca..030e79c 100644 --- a/pkg/events/text_message_event.go +++ b/pkg/events/text_message_event.go @@ -1,5 +1,15 @@ package events +// TextMessageEvent represents an event for a text message. type TextMessageEvent struct { - BaseMessageEvent + BaseMessageEvent `json:",inline"` + Text string `json:"text"` +} + +// NewTextMessageEvent creates a new TextMessageEvent instance. +func NewTextMessageEvent(baseMessageEvent BaseMessageEvent, text string) *TextMessageEvent { + return &TextMessageEvent{ + BaseMessageEvent: baseMessageEvent, + Text: text, + } } diff --git a/pkg/events/video_message_event.go b/pkg/events/video_message_event.go new file mode 100644 index 0000000..527abf2 --- /dev/null +++ b/pkg/events/video_message_event.go @@ -0,0 +1,22 @@ +package events + +import "github.com/sarthakjdev/wapi.go/pkg/components" + +// VideoMessageEvent represents a WhatsApp video message event. +type VideoMessageEvent struct { + BaseMediaMessageEvent `json:",inline"` + Video components.VideoMessage `json:"video"` +} + +// NewVideoMessageEvent creates a new VideoMessageEvent instance. +func NewVideoMessageEvent(baseMessageEvent BaseMessageEvent, video components.VideoMessage, mimeType, sha256, mediaId string) *VideoMessageEvent { + return &VideoMessageEvent{ + BaseMediaMessageEvent: BaseMediaMessageEvent{ + MediaId: mediaId, + Sha256: sha256, + MimeType: mimeType, + BaseMessageEvent: baseMessageEvent, + }, + Video: video, + } +} diff --git a/pkg/manager/base_manager.go b/pkg/manager/base_manager.go deleted file mode 100644 index 8686c15..0000000 --- a/pkg/manager/base_manager.go +++ /dev/null @@ -1,9 +0,0 @@ -package manager - -type BaseManager struct { -} - -func NewBaseManager() *BaseManager { - return &BaseManager{} -} - diff --git a/pkg/manager/media_manager.go b/pkg/manager/media_manager.go deleted file mode 100644 index 89c2af2..0000000 --- a/pkg/manager/media_manager.go +++ /dev/null @@ -1,21 +0,0 @@ -package manager - -type MediaManager struct { - BaseManager -} - -func NewMediaManager() *MediaManager { - return &MediaManager{} -} - -func (mm *MediaManager) UploadMedia() { - // Play media -} - -func (mm *MediaManager) GetMediaUrlById() { - // Play media -} - -func (mm *MediaManager) GetMediaIdByUrl() { - // Play media -} diff --git a/pkg/manager/message_manager.go b/pkg/manager/message_manager.go deleted file mode 100644 index 0be1e12..0000000 --- a/pkg/manager/message_manager.go +++ /dev/null @@ -1,21 +0,0 @@ -package manager - -import "github.com/sarthakjdev/wapi.go/pkg/client" - -type MessageManager struct { - BaseManager - client client.Client -} - -func NewMessageManager() *MessageManager { - return &MessageManager{} -} - -func (mm *MessageManager) Send() { - - // Send message -} - -func (mm *MessageManager) Reply() { - // Reply to message -} diff --git a/pkg/manager/phone_manager.go b/pkg/manager/phone_manager.go deleted file mode 100644 index 3c36d38..0000000 --- a/pkg/manager/phone_manager.go +++ /dev/null @@ -1,9 +0,0 @@ -package manager - -type PhoneNumbersManager struct { - BaseManager -} - -func (*PhoneNumbersManager) NewPhoneNumbersManager() *PhoneNumbersManager { - return &PhoneNumbersManager{} -} diff --git a/pkg/models/base_message.go b/pkg/models/base_message.go deleted file mode 100644 index db26a5a..0000000 --- a/pkg/models/base_message.go +++ /dev/null @@ -1,4 +0,0 @@ -package types - -type BaseMessage struct { -} diff --git a/pkg/models/text_message.go b/pkg/models/text_message.go deleted file mode 100644 index cb8cd08..0000000 --- a/pkg/models/text_message.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -type TextMessage struct { - // embedding - BaseMessage -} diff --git a/pkg/webhook/handler.go b/pkg/webhook/handler.go deleted file mode 100644 index 1bc4d36..0000000 --- a/pkg/webhook/handler.go +++ /dev/null @@ -1,4 +0,0 @@ -package webhook - -func getRequestHandler() { -} diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go deleted file mode 100644 index 4cb0012..0000000 --- a/pkg/webhook/webhook.go +++ /dev/null @@ -1,8 +0,0 @@ -package webhook - -type webhook struct { -} - -func NewWebhook() *webhook { - return &webhook{} -} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..7bab701 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,10 @@ +package utils + +import ( + "github.com/go-playground/validator/v10" +) + +// return a validator instance +func GetValidator() *validator.Validate { + return validator.New() +}