diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 984137d..605fafd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,32 +18,31 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - name: Set up Go 1.17 - uses: actions/setup-go@v2 + uses: actions/checkout@v4 + - name: Set up Go 1.24 + uses: actions/setup-go@v5 with: - # TODO: Should migrate to 1.18 later - go-version: '1.17' + go-version: '1.24' - name: Get dependencies run: go mod tidy - name: Lint code with golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.50.1 + version: v1.62.2 only-new-issues: true tests: name: Tests runs-on: ubuntu-latest strategy: matrix: - go-version: ['1.13', '1.14', '1.15', '1.16', '1.17'] + go-version: ['1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22', '1.23', '1.24'] steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Get dependencies run: go mod tidy - name: Tests diff --git a/.golangci.yml b/.golangci.yml index 0a739bc..99585ab 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,31 +1,16 @@ run: skip-dirs-use-default: true allow-parallel-runners: true + modules-download-mode: readonly output: - format: colored-line-number + formats: + - format: colored-line-number sort-results: true -# Linters below do not support go1.18 yet because of generics. -# See https://github.com/golangci/golangci-lint/issues/2649 -# - bodyclose -# - sqlclosecheck - linters: disable-all: true enable: - - paralleltest - - tparallel - - asciicheck - - asasalint - - varnamelen - - reassign - - nilnil - - nilerr - - nakedret - - goprintffuncname - - typecheck - - errchkjson - errcheck - gosimple - govet @@ -33,11 +18,12 @@ linters: - staticcheck - unused - unparam + - bodyclose - dogsled - dupl - errorlint - exhaustive - - exportloopref + - copyloopvar - funlen - gocognit - goconst @@ -46,16 +32,18 @@ linters: - godot - goimports - revive + - mnd - gosec - - lll - makezero - misspell - nestif - prealloc - predeclared - - exportloopref + - sqlclosecheck - unconvert - whitespace + - unused + - testifylint linters-settings: govet: @@ -64,11 +52,9 @@ linters-settings: enable: - assign - atomic - - atomicalign - bools - buildtag - copylocks - - fieldalignment - httpresponse - loopclosure - lostcancel @@ -80,6 +66,7 @@ linters-settings: - unmarshal - unreachable - unsafeptr + - unused settings: printf: funcs: @@ -138,20 +125,18 @@ linters-settings: threshold: 200 errorlint: errorf: true - asserts: false - comparison: false exhaustive: check-generated: false default-signifies-exhaustive: false funlen: - lines: 90 - statements: 40 + lines: 65 + statements: 50 gocognit: min-complexity: 25 gocyclo: min-complexity: 25 goimports: - local-prefixes: github.com/retailcrm/messenger + local-prefixes: github.com/retailcrm/mg-transport-api-client-go lll: line-length: 120 misspell: @@ -161,25 +146,23 @@ linters-settings: whitespace: multi-if: false multi-func: false - varnamelen: - max-distance: 10 - ignore-map-index-ok: true - ignore-type-assert-ok: true - ignore-chan-recv-ok: true - ignore-decls: - - t *testing.T - - e error - - i int + issues: exclude-rules: - path: _test\.go linters: + - dupl + - mnd - lll + - bodyclose - errcheck + - sqlclosecheck - misspell - ineffassign - whitespace - makezero + - maligned + - ifshort - errcheck - funlen - goconst @@ -187,10 +170,6 @@ issues: - gocyclo - godot - unused - - errchkjson - - varnamelen - - path: \.go - text: "Error return value of `io.WriteString` is not checked" exclude-use-default: true exclude-case-sensitive: false max-issues-per-linter: 0 @@ -202,4 +181,4 @@ severity: case-sensitive: false service: - golangci-lint-version: 1.50.x + golangci-lint-version: 1.62.x \ No newline at end of file diff --git a/examples/basic/main.go b/examples/basic/main.go index 1b12ad9..a33cbeb 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -48,7 +48,8 @@ func main() { fmt.Println("Something went wrong!", err) } - r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, "") + r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), + messenger.ResponseType, "", messenger.NotificationRegularType) }) // Setup a handler to be triggered when a message is delivered diff --git a/examples/extension/main.go b/examples/extension/main.go index 853c0a7..c4034b3 100644 --- a/examples/extension/main.go +++ b/examples/extension/main.go @@ -57,7 +57,8 @@ func main() { fmt.Println("Something went wrong!", err) } - r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, "") + r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, + "", messenger.NotificationRegularType) }) addr := fmt.Sprintf("%s:%d", *host, *port) diff --git a/examples/linked-account/main.go b/examples/linked-account/main.go index 67bdc92..557975d 100644 --- a/examples/linked-account/main.go +++ b/examples/linked-account/main.go @@ -84,7 +84,8 @@ func main() { text = "You've been logged out of your account." } - if _, err := r.Text(text, messenger.ResponseType, ""); err != nil { + if _, err := r.Text(text, messenger.ResponseType, + "", messenger.NotificationRegularType); err != nil { log.Println("Failed to send account linking feedback") } }) @@ -134,7 +135,8 @@ func logoutButton(r *messenger.Response) error { // greeting salutes the user. func greeting(p messenger.Profile, r *messenger.Response) error { - _, err := r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, "") + _, err := r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, + "", messenger.NotificationRegularType) return err } @@ -156,7 +158,7 @@ func help(p messenger.Profile, r *messenger.Response) error { }, } - _, err := r.TextWithReplies(text, replies, messenger.ResponseType, "") + _, err := r.TextWithReplies(text, replies, messenger.ResponseType, "", messenger.NotificationRegularType) return err } diff --git a/go.mod b/go.mod index 18da7b6..26c9513 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ require ( golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 ) +go 1.16 + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/messenger.go b/messenger.go index 66e2dae..cc6103b 100644 --- a/messenger.go +++ b/messenger.go @@ -15,20 +15,20 @@ import ( ) const ( - // ProfileURL is the API endpoint used for retrieving profiles. - // Used in the form: https://graph.facebook.com/v2.6/?fields=&access_token= - ProfileURL = "https://graph.facebook.com/v2.6/" - // ProfileFields is a list of JSON field names which will be populated by the profile query. ProfileFields = "first_name,last_name,profile_pic" + // ProfileURL is the API endpoint used for retrieving profiles. + // Used in the form: https://graph.facebook.com/v14.0/?fields=&access_token= + ProfileURL = "https://graph.facebook.com/v14.0/" + // SendSettingsURL is API endpoint for saving settings. - SendSettingsURL = "https://graph.facebook.com/v2.6/me/thread_settings" + SendSettingsURL = "https://graph.facebook.com/v11.0/me/thread_settings" // MessengerProfileURL is the API endpoint where you set properties that define various aspects of the following Messenger Platform features. // Used in the form https://graph.facebook.com/v2.6/me/messenger_profile?access_token= // https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/ - MessengerProfileURL = "https://graph.facebook.com/v2.6/me/messenger_profile" + MessengerProfileURL = "https://graph.facebook.com/v11.0/me/messenger_profile" ) // Options are the settings used when creating a Messenger client. @@ -303,8 +303,8 @@ func (m *Messenger) handle(w http.ResponseWriter, r *http.Request) { return } - if rec.Object != "page" { - fmt.Println("Object is not page, undefined behavior. Got", rec.Object) + if rec.Object != "page" && rec.Object != "instagram" { + fmt.Println("Object is not page or instagram, undefined behavior. Got", rec.Object) respond(w, http.StatusUnprocessableEntity) return } @@ -443,7 +443,7 @@ func (m *Messenger) Response(to int64) *Response { } // Send will send a textual message to a user. This user must have previously initiated a conversation with the bot. -func (m *Messenger) Send(to Recipient, message string, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) { +func (m *Messenger) Send(to Recipient, message string, messagingType MessagingType, metadata string, tags ...TagType) (QueryResponse, error) { return m.SendWithReplies(to, message, nil, messagingType, metadata, tags...) } @@ -458,14 +458,14 @@ func (m *Messenger) SendGeneralMessage(to Recipient, elements *[]StructuredMessa } // SendWithReplies sends a textual message to a user, but gives them the option of numerous quick response options. -func (m *Messenger) SendWithReplies(to Recipient, message string, replies []QuickReply, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) { +func (m *Messenger) SendWithReplies(to Recipient, message string, replies []QuickReply, messagingType MessagingType, metadata string, tags ...TagType) (QueryResponse, error) { response := &Response{ token: m.token, to: to, sendAPIVersion: m.sendAPIVersion, } - return response.TextWithReplies(message, replies, messagingType, metadata, tags...) + return response.TextWithReplies(message, replies, messagingType, metadata, NotificationRegularType, tags...) } // Attachment sends an image, sound, video or a regular file to a given recipient. diff --git a/profile.go b/profile.go index 52e909c..188565d 100644 --- a/profile.go +++ b/profile.go @@ -9,4 +9,13 @@ type Profile struct { Locale string `json:"locale"` Timezone float64 `json:"timezone"` Gender string `json:"gender"` + + // instagram user profile + Username string `json:"username,omitempty"` + IsPrivate bool `json:"is_private,omitempty"` + // FollowCount int32 `json:"follow_count,omitempty"` + FollowedByCount int32 `json:"follower_count,omitempty"` // by the documentation followed_by_count + IsVerifiedUser bool `json:"is_verified_user"` + IsUserFollowBusiness bool `json:"is_user_follow_business"` + IsBusinessFollowUser bool `json:"is_business_follow_user"` } diff --git a/response.go b/response.go index 430a24c..71b1f25 100644 --- a/response.go +++ b/response.go @@ -19,12 +19,14 @@ import ( // AttachmentType is attachment type. type AttachmentType string type MessagingType string +type NotificationType string type TopElementStyle string type ImageAspectRatio string +type TagType string const ( // DefaultSendAPIVersion is a default Send API version - DefaultSendAPIVersion = "v2.11" + DefaultSendAPIVersion = "v11.0" // SendMessageURL is API endpoint for sending messages. SendMessageURL = "https://graph.facebook.com/%s/me/messages" // ThreadControlURL is the API endpoint for passing thread control. @@ -50,6 +52,13 @@ const ( // NonPromotionalSubscriptionType is NON_PROMOTIONAL_SUBSCRIPTION messaging type. NonPromotionalSubscriptionType MessagingType = "NON_PROMOTIONAL_SUBSCRIPTION" + // NotificationNoPushType is NO_PUSH notification type. No notification. + NotificationNoPushType NotificationType = "NO_PUSH" + // NotificationRegularType is REGULAR notification type (default). Sound or vibration when a message is received by a person. + NotificationRegularType NotificationType = "REGULAR" + // NotificationSilentPushType is SILENT_PUSH notification type. On-screen notification only. + NotificationSilentPushType NotificationType = "SILENT_PUSH" + // TopElementStyle is compact. CompactTopElementStyle TopElementStyle = "compact" // TopElementStyle is large. @@ -59,6 +68,21 @@ const ( HorizontalImageAspectRatio ImageAspectRatio = "horizontal" // ImageAspectRatio is square. SquareImageAspectRatio ImageAspectRatio = "square" + + SenderActionMarkSeen SenderAction = "mark_seen" + SenderActionTypingOn SenderAction = "typing_on" + SenderActionTypingOff SenderAction = "typing_off" + + // TagAccountUpdateType Tags the message you are sending to your customer as a non-recurring update to their application or account. Not available for Instagram Messaging API. + TagAccountUpdateType TagType = "ACCOUNT_UPDATE" + // TagConfirmedEventUpdateType Tags the message you are sending to your customer as a reminder fo an upcoming event or an update for an event in procgres for which the customer is registered. Not available for Instagram Messaging API. + TagConfirmedEventUpdateType TagType = "CONFIRMED_EVENT_UPDATE" + // TagCustomerFeedbackType Tags the message you are sending to your customer as a Customer Feedback Survey. Customer feedback messages must be sent within 7 days of the customer's last message. Not available for Instagram Messaging API. + TagCustomerFeedbackType TagType = "CUSTOMER_FEEDBACK" + // TagHumanAgentType When this tag is added to a message to a customer, it allows a human agent to respond to a person's message. Messages can be sent within 7 days of the person's. Human agent support is for issues that cannot be resolved within the standard 24 hour messaging window. + TagHumanAgentType TagType = "HUMAN_AGENT" + // TagPostPurchaseUpdateType Tags the message you are sending to your customer as an update for a recent purchase made by the customer. Not available for Instagram Messaging API. + TagPostPurchaseUpdateType TagType = "POST_PURCHASE_UPDATE" ) // QueryResponse is the response sent back by Facebook when setting up things @@ -123,17 +147,31 @@ func (r *Response) SetToken(token string) { r.token = token } -// Text sends a textual message. -func (r *Response) Text(message string, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) { - return r.TextWithReplies(message, nil, messagingType, metadata, tags...) +func (r *Response) Text( + message string, + messagingType MessagingType, + metadata string, + notificationType NotificationType, + tags ...TagType, +) (QueryResponse, error) { + return r.TextWithReplies(message, nil, messagingType, metadata, notificationType, tags...) } // TextWithReplies sends a textual message with some replies // messagingType should be one of the following: "RESPONSE","UPDATE","MESSAGE_TAG","NON_PROMOTIONAL_SUBSCRIPTION" // only supply tags when messagingType == "MESSAGE_TAG" // (see https://developers.facebook.com/docs/messenger-platform/send-messages#messaging_types for more). -func (r *Response) TextWithReplies(message string, replies []QuickReply, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) { - var tag string +// notificationType should be one of the following: "NO_PUSH","REGULAR" (default),"SILENT_PUSH" +// only supply tags when messagingType == "MESSAGE_TAG" (see https://developers.facebook.com/docs/messenger-platform/send-messages#messaging_types for more). +func (r *Response) TextWithReplies( + message string, + replies []QuickReply, + messagingType MessagingType, + metadata string, + notificationType NotificationType, + tags ...TagType, +) (QueryResponse, error) { + var tag TagType if len(tags) > 0 { tag = tags[0] } @@ -147,14 +185,22 @@ func (r *Response) TextWithReplies(message string, replies []QuickReply, messagi QuickReplies: replies, Metadata: metadata, }, - Tag: tag, + Tag: tag, + NotificationType: notificationType, } return r.DispatchMessage(&m) } // AttachmentWithReplies sends a attachment message with some replies. -func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment, replies []QuickReply, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) { - var tag string +func (r *Response) AttachmentWithReplies( + attachment *StructuredMessageAttachment, + replies []QuickReply, + messagingType MessagingType, + metadata string, + notificationType NotificationType, + tags ...TagType, +) (QueryResponse, error) { + var tag TagType if len(tags) > 0 { tag = tags[0] } @@ -167,7 +213,8 @@ func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment QuickReplies: replies, Metadata: metadata, }, - Tag: tag, + Tag: tag, + NotificationType: notificationType, } return r.DispatchMessage(&m) } @@ -434,10 +481,11 @@ func (r *Response) PassThreadToInbox() error { // SendMessage is the information sent in an API request to Facebook. type SendMessage struct { - MessagingType MessagingType `json:"messaging_type"` - Recipient Recipient `json:"recipient"` - Message MessageData `json:"message"` - Tag string `json:"tag,omitempty"` + MessagingType MessagingType `json:"messaging_type"` + Recipient Recipient `json:"recipient"` + Message MessageData `json:"message"` + Tag TagType `json:"tag,omitempty"` + NotificationType NotificationType `json:"notification_type,omitempty"` } // MessageData is a message consisting of text or an attachment, with an additional selection of optional quick replies.