diff --git a/readme.md b/readme.md index 4af4fa2..d87e9a5 100644 --- a/readme.md +++ b/readme.md @@ -99,29 +99,33 @@ You can fork or edit this source code ! You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API, furthermore you can generate HTTP Client from this API using [openapi-generator](https://openapi-generator.tech/#try) -| Feature | Menu | Method | URL | -|---------|-------------------------|--------|-----------------------------| -| ✅ | Login | GET | /app/login | -| ✅ | Logout | GET | /app/logout | -| ✅ | Reconnect | GET | /app/reconnect | -| ✅ | User Info | GET | /user/info | -| ✅ | User Avatar | GET | /user/avatar | -| ✅ | User My Group List | GET | /user/my/groups | -| ✅ | User My Privacy Setting | GET | /user/my/privacy | -| ✅ | Send Message | POST | /send/message | -| ✅ | Send Image | POST | /send/image | -| ✅ | Send Audio | POST | /send/audio | -| ✅ | Send File | POST | /send/file | -| ✅ | Send Video | POST | /send/video | -| ✅ | Send Contact | POST | /send/contact | -| ✅ | Send Link | POST | /send/link | -| ✅ | Send Location | POST | /send/location | -| ✅ | Send Poll / Vote | POST | /send/poll | -| ✅ | Revoke Message | POST | /message/:message_id/revoke | -| ✅ | React Message | POST | /message/:message_id/react | -| ✅ | Edit Message | POST | /message/:message_id/update | -| ✅ | Join Group With Link | POST | /group/join-with-link | -| ✅ | Leave Group | POST | /group/leave | +| Feature | Menu | Method | URL | +|---------|--------------------------------|--------|-----------------------------| +| ✅ | Login | GET | /app/login | +| ✅ | Logout | GET | /app/logout | +| ✅ | Reconnect | GET | /app/reconnect | +| ✅ | User Info | GET | /user/info | +| ✅ | User Avatar | GET | /user/avatar | +| ✅ | User My Group List | GET | /user/my/groups | +| ✅ | User My Privacy Setting | GET | /user/my/privacy | +| ✅ | Send Message | POST | /send/message | +| ✅ | Send Image | POST | /send/image | +| ✅ | Send Audio | POST | /send/audio | +| ✅ | Send File | POST | /send/file | +| ✅ | Send Video | POST | /send/video | +| ✅ | Send Contact | POST | /send/contact | +| ✅ | Send Link | POST | /send/link | +| ✅ | Send Location | POST | /send/location | +| ✅ | Send Poll / Vote | POST | /send/poll | +| ✅ | Revoke Message | POST | /message/:message_id/revoke | +| ✅ | React Message | POST | /message/:message_id/react | +| ✅ | Edit Message | POST | /message/:message_id/update | +| ✅ | Join Group With Link | POST | /group/join-with-link | +| ✅ | Leave Group | POST | /group/leave | +| ✅ | Create Group | POST | /group/leave | +| ❌ | Add More Participants in Group | POST | | +| ❌ | Remove Participant in Group | POST | | +| ❌ | Promote Participant in Group | POST | | ``` ✅ = Available @@ -130,7 +134,7 @@ API using [openapi-generator](https://openapi-generator.tech/#try) ### App User Interface -1. Homepage  +1. Homepage  2. Login  3. Send Message  4. Send Image  diff --git a/src/config/settings.go b/src/config/settings.go index e1d89eb..7581678 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -6,7 +6,7 @@ import ( ) var ( - AppVersion = "v4.11.1" + AppVersion = "v4.12.0" AppPort = "3000" AppDebug = false AppOs = fmt.Sprintf("AldinoKemal") @@ -25,4 +25,6 @@ var ( WhatsappWebhook string WhatsappSettingMaxFileSize int64 = 50000000 // 50MB WhatsappSettingMaxVideoSize int64 = 100000000 // 100MB + WhatsappTypeUser = "@s.whatsapp.net" + WhatsappTypeGroup = "@g.us" ) diff --git a/src/domains/group/group.go b/src/domains/group/group.go index 33a6c9b..689cce9 100644 --- a/src/domains/group/group.go +++ b/src/domains/group/group.go @@ -5,6 +5,7 @@ import "context" type IGroupService interface { JoinGroupWithLink(ctx context.Context, request JoinGroupWithLinkRequest) (groupID string, err error) LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error) + CreateGroup(ctx context.Context, request CreateGroupRequest) (groupID string, err error) } type JoinGroupWithLinkRequest struct { @@ -14,3 +15,8 @@ type JoinGroupWithLinkRequest struct { type LeaveGroupRequest struct { GroupID string `json:"group_id" form:"group_id"` } + +type CreateGroupRequest struct { + Title string `json:"title" form:"title"` + Participants []string `json:"participants" form:"participants"` +} diff --git a/src/internal/rest/group.go b/src/internal/rest/group.go index 1d7f2d9..9edb2d7 100644 --- a/src/internal/rest/group.go +++ b/src/internal/rest/group.go @@ -1,6 +1,7 @@ package rest import ( + "fmt" domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/gofiber/fiber/v2" @@ -12,6 +13,7 @@ type Group struct { func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group { rest := Group{Service: service} + app.Post("/group", rest.CreateGroup) app.Post("/group/join-with-link", rest.JoinGroupWithLink) app.Post("/group/leave", rest.LeaveGroup) return rest @@ -49,3 +51,21 @@ func (controller *Group) LeaveGroup(c *fiber.Ctx) error { Message: "Success leave group", }) } + +func (controller *Group) CreateGroup(c *fiber.Ctx) error { + var request domainGroup.CreateGroupRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + groupID, err := controller.Service.CreateGroup(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: fmt.Sprintf("Success created group with id %s", groupID), + Results: map[string]string{ + "group_id": groupID, + }, + }) +} diff --git a/src/pkg/error/whatsapp_error.go b/src/pkg/error/whatsapp_error.go index db2432a..1c71b6b 100644 --- a/src/pkg/error/whatsapp_error.go +++ b/src/pkg/error/whatsapp_error.go @@ -71,6 +71,7 @@ func (e WaUploadMediaError) StatusCode() int { } const ( - ErrInvalidJID = InvalidJID("your JID is invalid") - ErrWaCLI = WaCliError("your WhatsApp CLI is invalid or empty") + ErrInvalidJID = InvalidJID("your JID is invalid") + ErrUserNotRegistered = InvalidJID("user is not registered") + ErrWaCLI = WaCliError("your WhatsApp CLI is invalid or empty") ) diff --git a/src/pkg/whatsapp/whatsapp.go b/src/pkg/whatsapp/whatsapp.go index b0043db..333dcd7 100644 --- a/src/pkg/whatsapp/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -55,9 +55,9 @@ type evtMessage struct { func SanitizePhone(phone *string) { if phone != nil && len(*phone) > 0 && !strings.Contains(*phone, "@") { if len(*phone) <= 15 { - *phone = fmt.Sprintf("%s@s.whatsapp.net", *phone) + *phone = fmt.Sprintf("%s%s", *phone, config.WhatsappTypeUser) } else { - *phone = fmt.Sprintf("%s@g.us", *phone) + *phone = fmt.Sprintf("%s%s", *phone, config.WhatsappTypeGroup) } } } @@ -116,7 +116,7 @@ func ParseJID(arg string) (types.JID, error) { } } -func isOnWhatsapp(waCli *whatsmeow.Client, jid string) bool { +func IsOnWhatsapp(waCli *whatsmeow.Client, jid string) bool { // only check if the jid a user with @s.whatsapp.net if strings.Contains(jid, "@s.whatsapp.net") { data, err := waCli.IsOnWhatsApp([]string{jid}) @@ -137,7 +137,7 @@ func isOnWhatsapp(waCli *whatsmeow.Client, jid string) bool { func ValidateJidWithLogin(waCli *whatsmeow.Client, jid string) (types.JID, error) { MustLogin(waCli) - if !isOnWhatsapp(waCli, jid) { + if !IsOnWhatsapp(waCli, jid) { return types.JID{}, pkgError.InvalidJID(fmt.Sprintf("Phone %s is not on whatsapp", jid)) } diff --git a/src/services/group.go b/src/services/group.go index ba57f68..c386639 100644 --- a/src/services/group.go +++ b/src/services/group.go @@ -2,10 +2,13 @@ package services import ( "context" + "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types" ) type groupService struct { @@ -43,3 +46,37 @@ func (service groupService) LeaveGroup(ctx context.Context, request domainGroup. return service.WaCli.LeaveGroup(JID) } + +func (service groupService) CreateGroup(ctx context.Context, request domainGroup.CreateGroupRequest) (groupID string, err error) { + if err = validations.ValidateCreateGroup(ctx, request); err != nil { + return groupID, err + } + whatsapp.MustLogin(service.WaCli) + + var participantsJID []types.JID + for _, participant := range request.Participants { + formattedParticipant := participant + config.WhatsappTypeUser + + if !whatsapp.IsOnWhatsapp(service.WaCli, formattedParticipant) { + return "", pkgError.ErrUserNotRegistered + } + + if participantJID, err := types.ParseJID(formattedParticipant); err == nil { + participantsJID = append(participantsJID, participantJID) + } + } + + groupConfig := whatsmeow.ReqCreateGroup{ + Name: request.Title, + Participants: participantsJID, + GroupParent: types.GroupParent{}, + GroupLinkedParent: types.GroupLinkedParent{}, + } + + groupInfo, err := service.WaCli.CreateGroup(groupConfig) + if err != nil { + return + } + + return groupInfo.JID.String(), nil +} diff --git a/src/validations/group_validation.go b/src/validations/group_validation.go index 35d35b6..69742e5 100644 --- a/src/validations/group_validation.go +++ b/src/validations/group_validation.go @@ -30,3 +30,17 @@ func ValidateLeaveGroup(ctx context.Context, request domainGroup.LeaveGroupReque return nil } + +func ValidateCreateGroup(ctx context.Context, request domainGroup.CreateGroupRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, + validation.Field(&request.Title, validation.Required), + validation.Field(&request.Participants, validation.Required), + validation.Field(&request.Participants, validation.Each(validation.Required)), + ) + + if err != nil { + return pkgError.ValidationError(err.Error()) + } + + return nil +} diff --git a/src/views/components/GroupCreate.js b/src/views/components/GroupCreate.js new file mode 100644 index 0000000..e4c66ed --- /dev/null +++ b/src/views/components/GroupCreate.js @@ -0,0 +1,114 @@ +export default { + name: 'CreateGroup', + data() { + return { + loading: false, + title: '', + participants: ['', ''], + } + }, + methods: { + openModal() { + $('#modalGroupCreate').modal({ + onApprove: function () { + return false; + } + }).modal('show'); + }, + handleAddParticipant() { + this.participants.push('') + }, + handleDeleteParticipant(index) { + this.participants.splice(index, 1) + }, + async handleSubmit() { + try { + let response = await this.submitApi() + showSuccessInfo(response) + $('#modalGroupCreate').modal('hide'); + } catch (err) { + showErrorInfo(err) + } + }, + async submitApi() { + this.loading = true; + try { + let response = await window.http.post(`/group`, { + title: this.title, + // convert participant become list of string + participants: this.participants.filter(participant => participant !== '').map(participant => `${participant}`) + }) + this.handleReset(); + return response.data.message; + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message); + } + throw new Error(error.message); + } finally { + this.loading = false; + } + }, + handleReset() { + this.title = ''; + this.participants = ['', '']; + }, + }, + template: ` +