Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
f9c1b24194 | |||
a58e4100e1 | |||
7b9e2a8d29 | |||
d81730e3e7 | |||
08bfe83ba4 | |||
c0f3da8060 | |||
5727a20506 | |||
3e9851b2c0 | |||
|
8f23e04eea | ||
|
c2dbe7021e | ||
|
6eba33b3be | ||
|
7961d30a51 | ||
|
12931fb275 | ||
|
65299d9606 | ||
|
aa216ed4af | ||
|
28b2d85660 | ||
|
8dd5993481 | ||
|
042fb9a951 | ||
|
b6715c2375 | ||
|
c5d6c8a29c | ||
|
d6a8b0818f | ||
|
da7bc977a3 | ||
|
eeb2f88b97 | ||
|
784cfe93c1 | ||
|
6d42a230d1 | ||
|
7166300503 | ||
|
f92a8a7a86 | ||
|
646bec0e27 | ||
|
b9317e3e37 | ||
|
05eb619c40 | ||
|
055fdc7126 | ||
|
3bac8c4f92 | ||
|
8fca7782ec | ||
|
8628d37bef | ||
|
6f453ff6cc | ||
|
73586d4b4c | ||
|
7f8d829ff7 | ||
|
d657643538 | ||
|
3e663f8615 | ||
|
eea12a3b50 | ||
|
e6f29c4e73 | ||
|
cd2551461d | ||
|
2ff9a03a8c | ||
|
f900b99dac | ||
|
d8cdcf4ef2 | ||
|
010fd832ac | ||
|
bd76de2ffe | ||
|
537a97ebe7 | ||
|
9ee368f10d | ||
|
d9dec16e27 | ||
|
755d681f86 | ||
|
f0aa4a6d73 | ||
|
c6f6857cfa | ||
|
31885d2726 |
21
.github/workflows/go-test.yml
vendored
Normal file
21
.github/workflows/go-test.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Go Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.15
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v ./...
|
27
.github/workflows/golangci-lint.yml
vendored
Normal file
27
.github/workflows/golangci-lint.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: golangci-lint
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
with:
|
||||||
|
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||||
|
version: v1.29
|
||||||
|
|
||||||
|
# Optional: working directory, useful for monorepos
|
||||||
|
# working-directory: somedir
|
||||||
|
|
||||||
|
# Optional: golangci-lint command line arguments.
|
||||||
|
# args: --issues-exit-code=0
|
||||||
|
|
||||||
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
|
# only-new-issues: true
|
||||||
|
|
||||||
|
# Optional: if set to true then the action will use pre-installed Go
|
||||||
|
# skip-go-installation: true
|
88
README.md
Normal file → Executable file
88
README.md
Normal file → Executable file
@ -1,2 +1,86 @@
|
|||||||
# groupme
|
# Version 1.0 Release Date: TBD
|
||||||
GroupMe Library
|
I would like to add common helper functions/features inspired by the package use in the community. So please, especially before Version 1.0 release, let me know what you would like to see added to the package, but bear in mind the main objective to be a simple wrapper for the API exposed by the GroupMe team.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
# GroupMe API Wrapper
|
||||||
|

|
||||||
|
[](https://pkg.go.dev/github.com/densestvoid/groupme)
|
||||||
|
## Description
|
||||||
|
The design of this package is meant to be super simple. Wrap the exposed API endpoints [documented](https://dev.groupme.com/docs/v3#v3) by the GroupMe team. While you can achieve the core of this package with cURL, there are some small added features, coupled along with a modern language, that should simplify writing GroupMe [bots](https://dev.groupme.com/bots) and [applications](https://dev.groupme.com/applications).
|
||||||
|
|
||||||
|
[*FUTURE*] In addition to the Go package, there is also a CLI application built using this package; all the features are available from the command line.
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
I enjoy programming, I use GroupMe with friends, and I wanted to write a fun add-on application for our group. I happened to start using Go around this time, so it was good practice.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/densestvoid/groupme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is not a real token. Please find yours by logging
|
||||||
|
// into the GroupMe development website: https://dev.groupme.com/
|
||||||
|
const authorizationToken = "0123456789ABCDEF"
|
||||||
|
|
||||||
|
// A short program that gets the gets the first 5 groups
|
||||||
|
// the user is part of, and then the first 10 messages of
|
||||||
|
// the first group in that list
|
||||||
|
func main() {
|
||||||
|
// Create a new client with your auth token
|
||||||
|
client := groupme.NewClient(authorizationToken)
|
||||||
|
|
||||||
|
// Get the groups your user is part of
|
||||||
|
groups, err := client.IndexGroups(&groupme.GroupsQuery{
|
||||||
|
Page: 0,
|
||||||
|
PerPage: 5,
|
||||||
|
Omit: "memberships",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(groups)
|
||||||
|
|
||||||
|
// Get first 10 messages of the first group
|
||||||
|
if len(groups) <= 0 {
|
||||||
|
fmt.Println("No groups")
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err := client.IndexMessages(groups[0].ID, &groupme.IndexMessagesQuery{
|
||||||
|
Limit: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(messages)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Go Package
|
||||||
|
`go get github.com/densestvoid/groupme`
|
||||||
|
|
||||||
|
### [*FUTURE*] CLI
|
||||||
|
|
||||||
|
## Support
|
||||||
|
You can join the [GroupMe support group](https://groupme.com/join_group/65686806/il1737tE) (you will need to provide a reason for joining), or the [Discord server](https://discord.gg/raAdxWuKTU).
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
I find the hours I can spend developing personal projects decreasing every year, so I welcome any help I can get. Feel free to tackle any open issues, or if a feature request catches your eye, feel free to reach out to me and we can discuss adding it to the package. However, once version 1.0 is released, I don't foresee much work happening on this project unless the GroupMe API is updated.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
All credits for the actual platform belong to the GroupMe team; I only used the exposed API they wrote.
|
||||||
|
|
||||||
|
## License
|
||||||
|
GPL-3.0 License © [DensestVoid](https://github.com/densestvoid)
|
||||||
|
115
blocks_api.go
Normal file
115
blocks_api.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#blocks
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
blocksEndpointRoot = "/blocks"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
indexBlocksEndpoint = blocksEndpointRoot // GET
|
||||||
|
blockBetweenEndpoint = blocksEndpointRoot + "/between" // GET
|
||||||
|
createBlockEndpoint = blocksEndpointRoot // POST
|
||||||
|
unblockEndpoint = blocksEndpointRoot // DELETE
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// IndexBlock - A list of contacts you have blocked. These people cannot DM you
|
||||||
|
func (c *Client) IndexBlock(ctx context.Context, userID string, authToken string) ([]*Block, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+indexBlocksEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("user", userID)
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Blocks []*Block `json:"blocks"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Blocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockBetween - Asks if a block exists between you and another user id
|
||||||
|
func (c *Client) BlockBetween(ctx context.Context, userID, otherUserID string, authToken string) (bool, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+blockBetweenEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("user", userID)
|
||||||
|
query.Set("otherUser", otherUserID)
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Between bool `json:"between"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Between, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBlock - Creates a block between you and the contact
|
||||||
|
func (c *Client) CreateBlock(ctx context.Context, userID, otherUserID string, authToken string) (*Block, error) {
|
||||||
|
httpReq, err := http.NewRequest("POST", c.endpointBase+createBlockEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("user", userID)
|
||||||
|
query.Set("otherUser", otherUserID)
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Block *Block `json:"block"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unblock - Removes block between you and other user
|
||||||
|
func (c *Client) Unblock(ctx context.Context, userID, otherUserID string, authToken string) error {
|
||||||
|
httpReq, err := http.NewRequest("DELETE", c.endpointBase+unblockEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("user", userID)
|
||||||
|
query.Set("otherUser", otherUserID)
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
128
blocks_api_test.go
Normal file
128
blocks_api_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BlocksAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *BlocksAPISuite) SetupSuite() {
|
||||||
|
s.handler = blocksTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlocksAPISuite) TestBlocksIndex() {
|
||||||
|
blocks, err := s.client.IndexBlock(context.Background(), "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(blocks)
|
||||||
|
for _, block := range blocks {
|
||||||
|
s.Assert().NotZero(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlocksAPISuite) TestBlocksBetween() {
|
||||||
|
between, err := s.client.BlockBetween(context.Background(), "1", "2")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().True(between)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlocksAPISuite) TestBlocksCreate() {
|
||||||
|
block, err := s.client.CreateBlock(context.Background(), "1", "2")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlocksAPISuite) TestBlocksUnblock() {
|
||||||
|
s.Assert().NoError(s.client.Unblock(context.Background(), "1", "2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlocksAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(BlocksAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func blocksTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Index
|
||||||
|
router.Path("/blocks").
|
||||||
|
Queries("user", "").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexBlock").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"blocked_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Block Between
|
||||||
|
router.Path("/blocks/between").
|
||||||
|
Queries("user", "", "otherUser", "").
|
||||||
|
Methods("GET").
|
||||||
|
Name("BlockBetween").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"between": true
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Block
|
||||||
|
router.Path("/blocks").
|
||||||
|
Queries("user", "", "otherUser", "").
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateBlock").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"block": {
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"blocked_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unblock
|
||||||
|
router.Path("/blocks").
|
||||||
|
Queries("user", "", "otherUser", "").
|
||||||
|
Methods("DELETE").
|
||||||
|
Name("Unblock").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
123
bots_api.go
Normal file
123
bots_api.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#bots
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
botsEndpointRoot = "/bots"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
createBotEndpoint = botsEndpointRoot // POST
|
||||||
|
postBotMessageEndpoint = botsEndpointRoot + "/post" // POST
|
||||||
|
indexBotsEndpoint = botsEndpointRoot // GET
|
||||||
|
destroyBotEndpoint = botsEndpointRoot + "/destroy" // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// CreateBot - Create a bot. See the Bots Tutorial (https://dev.groupme.com/tutorials/bots)
|
||||||
|
// for a full walkthrough.
|
||||||
|
func (c *Client) CreateBot(ctx context.Context, bot *Bot, authToken string) (*Bot, error) {
|
||||||
|
URL := c.endpointBase + createBotEndpoint
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
Bot *Bot `json:"bot,omitempty"`
|
||||||
|
}{
|
||||||
|
bot,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Bot
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostBotMessage - Post a message from a bot
|
||||||
|
// TODO: Move PostBotMessage to bot object, since it doesn't require access token
|
||||||
|
func (c *Client) PostBotMessage(ctx context.Context, botID ID, text string, pictureURL *string) error {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + postBotMessageEndpoint)
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
BotID ID `json:"bot_id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
PictureURL *string `json:",omitempty"`
|
||||||
|
}{
|
||||||
|
botID,
|
||||||
|
text,
|
||||||
|
pictureURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.do(ctx, httpReq, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexBots - list bots that you have created
|
||||||
|
func (c *Client) IndexBots(ctx context.Context, authToken string) ([]*Bot, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+indexBotsEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*Bot
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyBot - Remove a bot that you have created
|
||||||
|
func (c *Client) DestroyBot(ctx context.Context, botID ID, authToken string) error {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + destroyBotEndpoint)
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
BotID ID `json:"bot_id"`
|
||||||
|
}{
|
||||||
|
botID,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
}
|
123
bots_api_test.go
Normal file
123
bots_api_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotsAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *BotsAPISuite) SetupSuite() {
|
||||||
|
s.handler = botsTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BotsAPISuite) TestBotsCreate() {
|
||||||
|
bot, err := s.client.CreateBot(context.Background(), &Bot{
|
||||||
|
Name: "test",
|
||||||
|
GroupID: "1",
|
||||||
|
AvatarURL: "url.com",
|
||||||
|
CallbackURL: "otherURL.com",
|
||||||
|
DMNotification: true,
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(bot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BotsAPISuite) TestBotsPostMessage() {
|
||||||
|
err := s.client.PostBotMessage(context.Background(), "1", "test message", nil)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BotsAPISuite) TestBotsIndex() {
|
||||||
|
bots, err := s.client.IndexBots(context.Background())
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(bots)
|
||||||
|
for _, bot := range bots {
|
||||||
|
s.Assert().NotZero(bot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BotsAPISuite) TestBotsDestroy() {
|
||||||
|
s.Require().NoError(s.client.DestroyBot(context.Background(), "1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBotsAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(BotsAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func botsTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
authRouter := router.Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Create
|
||||||
|
authRouter.Path("/bots").
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateBot").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"bot_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "hal9000",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"callback_url": "https://example.com/bots/callback",
|
||||||
|
"dm_notification": false
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Post Message
|
||||||
|
router.Path("/bots/post").
|
||||||
|
Methods("POST").
|
||||||
|
Name("PostBotMessage").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Index
|
||||||
|
authRouter.Path("/bots").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexBots").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"bot_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "hal9000",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"callback_url": "https://example.com/bots/callback",
|
||||||
|
"dm_notification": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Destroy
|
||||||
|
authRouter.Path("/bots/destroy").
|
||||||
|
Methods("POST").
|
||||||
|
Name("DestroyBot").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
75
chats_api.go
Normal file
75
chats_api.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#chats
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
chatsEndpointRoot = "/chats"
|
||||||
|
|
||||||
|
indexChatsEndpoint = chatsEndpointRoot // GET
|
||||||
|
)
|
||||||
|
|
||||||
|
// IndexChatsQuery defines the optional URL parameters for IndexChats
|
||||||
|
type IndexChatsQuery struct {
|
||||||
|
// Page Number
|
||||||
|
Page int `json:"page"`
|
||||||
|
// Number of chats per page
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexChats - Returns a paginated list of direct message chats, or
|
||||||
|
// conversations, sorted by updated_at descending.
|
||||||
|
func (c *Client) IndexChats(ctx context.Context, req *IndexChatsQuery, authToken string) ([]*Chat, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+indexChatsEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
if req != nil {
|
||||||
|
if req.Page > 0 {
|
||||||
|
query.Set("page", strconv.Itoa(req.Page))
|
||||||
|
}
|
||||||
|
if req.PerPage > 0 {
|
||||||
|
query.Set("per_page", strconv.Itoa(req.PerPage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp []*Chat
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
func (c *Client) IndexRelations(ctx context.Context, authToken string) ([]*User, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", "https://api.groupme.com/v4"+"/relationships", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
|
||||||
|
query.Set("include_blocked", "true")
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp []*User
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
90
chats_api_test.go
Normal file
90
chats_api_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatsAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *ChatsAPISuite) SetupSuite() {
|
||||||
|
s.handler = chatsTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChatsAPISuite) TestChatsIndex() {
|
||||||
|
chats, err := s.client.IndexChats(
|
||||||
|
context.Background(),
|
||||||
|
&IndexChatsQuery{
|
||||||
|
Page: 1,
|
||||||
|
PerPage: 20,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(chats)
|
||||||
|
for _, chat := range chats {
|
||||||
|
s.Assert().NotZero(chat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatsAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ChatsAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func chatsTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Index
|
||||||
|
router.Path("/chats").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexChats").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"created_at": 1352299338,
|
||||||
|
"updated_at": 1352299338,
|
||||||
|
"last_message": {
|
||||||
|
"attachments": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"avatar_url": "https://i.groupme.com/200x200.jpeg.abcdef",
|
||||||
|
"conversation_id": "12345+67890",
|
||||||
|
"created_at": 1352299338,
|
||||||
|
"favorited_by": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "John Doe",
|
||||||
|
"recipient_id": "67890",
|
||||||
|
"sender_id": "12345",
|
||||||
|
"sender_type": "user",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"text": "Hello world",
|
||||||
|
"user_id": "12345"
|
||||||
|
},
|
||||||
|
"messages_count": 10,
|
||||||
|
"other_user": {
|
||||||
|
"avatar_url": "https://i.groupme.com/200x200.jpeg.abcdef",
|
||||||
|
"id": "12345",
|
||||||
|
"name": "John Doe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
120
client.go
Normal file
120
client.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMeAPIBase - Endpoints are added on to this to get the full URI.
|
||||||
|
// Overridable for testing
|
||||||
|
const GroupMeAPIBase = "https://api.groupme.com/v3"
|
||||||
|
|
||||||
|
// Client communicates with the GroupMe API to perform actions
|
||||||
|
// on the basic types, i.e. Listing, Creating, Destroying
|
||||||
|
type Client struct {
|
||||||
|
httpClient *http.Client
|
||||||
|
endpointBase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new GroupMe API Client
|
||||||
|
func NewClient() *Client {
|
||||||
|
return &Client{
|
||||||
|
// TODO: enable transport information passing in
|
||||||
|
httpClient: &http.Client{},
|
||||||
|
endpointBase: GroupMeAPIBase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close safely shuts down the Client
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
c.httpClient.CloseIdleConnections()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a json formatted string
|
||||||
|
func (c Client) String() string {
|
||||||
|
return marshal(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Handle parsing of nested interface type response ///*/
|
||||||
|
type jsonResponse struct {
|
||||||
|
Response response `json:"response"`
|
||||||
|
Meta `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJSONResponse(i interface{}) *jsonResponse {
|
||||||
|
return &jsonResponse{Response: response{i}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
i interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r response) UnmarshalJSON(bs []byte) error {
|
||||||
|
return json.NewDecoder(bytes.NewBuffer(bs)).Decode(r.i)
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorStatusCodeMin = 300
|
||||||
|
|
||||||
|
func (c Client) do(ctx context.Context, req *http.Request, i interface{}) error {
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
if req.Method == "POST" {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
getResp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer getResp.Body.Close()
|
||||||
|
|
||||||
|
var readBytes []byte
|
||||||
|
// Check Status Code is 1XX or 2XX
|
||||||
|
if getResp.StatusCode >= errorStatusCodeMin {
|
||||||
|
readBytes, err = ioutil.ReadAll(getResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
// We couldn't read the output. Oh well; generate the appropriate error type anyway.
|
||||||
|
return &Meta{
|
||||||
|
Code: HTTPStatusCode(getResp.StatusCode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := newJSONResponse(nil)
|
||||||
|
if err = json.Unmarshal(readBytes, &resp); err != nil {
|
||||||
|
// We couldn't parse the output. Oh well; generate the appropriate error type anyway.
|
||||||
|
return &Meta{
|
||||||
|
Code: HTTPStatusCode(getResp.StatusCode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &resp.Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
readBytes, err = ioutil.ReadAll(getResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := newJSONResponse(i)
|
||||||
|
if err := json.Unmarshal(readBytes, &resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) doWithAuthToken(ctx context.Context, req *http.Request, i interface{}, authToken string) error {
|
||||||
|
URL := req.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("token", authToken)
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
return c.do(ctx, req, i)
|
||||||
|
}
|
59
client_test.go
Normal file
59
client_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientSuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *ClientSuite) SetupSuite() {
|
||||||
|
serverMux := http.NewServeMux()
|
||||||
|
serverMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
_, err := w.Write([]byte("error"))
|
||||||
|
s.Require().NoError(err)
|
||||||
|
})
|
||||||
|
s.handler = serverMux
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) SetupTest() {
|
||||||
|
s.client = NewClient("")
|
||||||
|
s.Require().NotNil(s.client)
|
||||||
|
|
||||||
|
s.client.endpointBase = s.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) TestClient_Close() {
|
||||||
|
s.Assert().NoError(s.client.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) TestClient_do_PostContentType() {
|
||||||
|
req, err := http.NewRequest("POST", "", nil)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Assert().Error(s.client.do(context.Background(), req, struct{}{}))
|
||||||
|
s.Assert().EqualValues(req.Header.Get("Content-Type"), "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) TestClient_do_DoError() {
|
||||||
|
req, err := http.NewRequest("", "", nil)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Assert().Error(s.client.do(context.Background(), req, struct{}{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) TestClient_do_UnmarshalError() {
|
||||||
|
req, err := http.NewRequest("GET", s.addr, nil)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Assert().Error(s.client.do(context.Background(), req, struct{}{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ClientSuite))
|
||||||
|
}
|
95
data_types.go
Normal file
95
data_types.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/responses
|
||||||
|
|
||||||
|
// ID is an unordered alphanumeric string
|
||||||
|
type ID string
|
||||||
|
|
||||||
|
// Treated as a constant
|
||||||
|
var alphaNumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
|
||||||
|
|
||||||
|
// Valid checks if the ID string is alpha numeric
|
||||||
|
func (id ID) Valid() bool {
|
||||||
|
return alphaNumericRegex.MatchString(string(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id ID) String() string {
|
||||||
|
return string(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp is the number of seconds since the UNIX epoch
|
||||||
|
type Timestamp uint64
|
||||||
|
|
||||||
|
// FromTime returns the time.Time as a Timestamp
|
||||||
|
func FromTime(t time.Time) Timestamp {
|
||||||
|
return Timestamp(t.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTime returns the Timestamp as a UTC Time
|
||||||
|
func (t Timestamp) ToTime() time.Time {
|
||||||
|
return time.Unix(int64(t), 0).UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the Timestamp in the default time.Time string format
|
||||||
|
func (t Timestamp) String() string {
|
||||||
|
return t.ToTime().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhoneNumber is the country code plus the number of the user
|
||||||
|
type PhoneNumber string
|
||||||
|
|
||||||
|
// Treated as a constant
|
||||||
|
var phoneNumberRegex = regexp.MustCompile(`^\+\d+ \d{10}$`)
|
||||||
|
|
||||||
|
// Valid checks if the ID string is alpha numeric
|
||||||
|
func (pn PhoneNumber) Valid() bool {
|
||||||
|
return phoneNumberRegex.MatchString(string(pn))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pn PhoneNumber) String() string {
|
||||||
|
return string(pn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPStatusCode are returned by HTTP requests in
|
||||||
|
// the header and the json "meta" field
|
||||||
|
type HTTPStatusCode int
|
||||||
|
|
||||||
|
// Text used as constant name
|
||||||
|
const (
|
||||||
|
HTTPOk HTTPStatusCode = 200
|
||||||
|
HTTPCreated HTTPStatusCode = 201
|
||||||
|
HTTPNoContent HTTPStatusCode = 204
|
||||||
|
HTTPNotModified HTTPStatusCode = 304
|
||||||
|
HTTPBadRequest HTTPStatusCode = 400
|
||||||
|
HTTPUnauthorized HTTPStatusCode = 401
|
||||||
|
HTTPForbidden HTTPStatusCode = 403
|
||||||
|
HTTPNotFound HTTPStatusCode = 404
|
||||||
|
HTTPEnhanceYourCalm HTTPStatusCode = 420
|
||||||
|
HTTPInternalServerError HTTPStatusCode = 500
|
||||||
|
HTTPBadGateway HTTPStatusCode = 502
|
||||||
|
HTTPServiceUnavailable HTTPStatusCode = 503
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the description of the status code according to GroupMe
|
||||||
|
func (c HTTPStatusCode) String() string {
|
||||||
|
return map[HTTPStatusCode]string{
|
||||||
|
HTTPOk: "success",
|
||||||
|
HTTPCreated: "resource was created successfully",
|
||||||
|
HTTPNoContent: "resource was deleted successfully",
|
||||||
|
HTTPNotModified: "no new data to return",
|
||||||
|
HTTPBadRequest: "invalid format or data specified in the request",
|
||||||
|
HTTPUnauthorized: "authentication credentials missing or incorrect",
|
||||||
|
HTTPForbidden: "request refused due to update limits",
|
||||||
|
HTTPNotFound: "URI is invalid or resource does not exist",
|
||||||
|
HTTPEnhanceYourCalm: "application is being rate limited",
|
||||||
|
HTTPInternalServerError: "something unexpected occurred",
|
||||||
|
HTTPBadGateway: "GroupMe is down or being upgraded",
|
||||||
|
HTTPServiceUnavailable: "servers are overloaded, try again later",
|
||||||
|
}[c]
|
||||||
|
}
|
61
data_types_test.go
Normal file
61
data_types_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataTypesSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestID_Valid_True() {
|
||||||
|
var id ID = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
s.Assert().True(id.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestID_Valid_False() {
|
||||||
|
var id ID = "`~!@#$%^&*()_-+={[}]:;\"'<,>.?/|\\"
|
||||||
|
s.Assert().False(id.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestTimestamp_FromTime() {
|
||||||
|
t := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
expected := Timestamp(0)
|
||||||
|
actual := FromTime(t)
|
||||||
|
s.Assert().EqualValues(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestTimestamp_ToTime() {
|
||||||
|
t := Timestamp(0)
|
||||||
|
expected := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
actual := t.ToTime()
|
||||||
|
s.Assert().EqualValues(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestPhoneNumber_Valid_True() {
|
||||||
|
var pn PhoneNumber = "+1 0123456789"
|
||||||
|
s.Assert().True(pn.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestPhoneNumber_Valid_NoPlus() {
|
||||||
|
var pn PhoneNumber = "1 0123456789"
|
||||||
|
s.Assert().False(pn.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestPhoneNumber_Valid_NoSpace() {
|
||||||
|
var pn PhoneNumber = "+10123456789"
|
||||||
|
s.Assert().False(pn.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DataTypesSuite) TestPhoneNumber_Valid_BadLength() {
|
||||||
|
var pn PhoneNumber = "+1 01234567890"
|
||||||
|
s.Assert().False(pn.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataTypesSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(DataTypesSuite))
|
||||||
|
}
|
140
direct_messages_api.go
Normal file
140
direct_messages_api.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#direct_messages
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
directMessagesEndpointRoot = "/direct_messages"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
indexDirectMessagesEndpoint = directMessagesEndpointRoot // GET
|
||||||
|
createDirectMessageEndpoint = directMessagesEndpointRoot // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// IndexDirectMessagesQuery defines the optional URL parameters for IndexDirectMessages
|
||||||
|
type IndexDirectMessagesQuery struct {
|
||||||
|
// Returns 20 messages created before the given message ID
|
||||||
|
BeforeID ID `json:"before_id"`
|
||||||
|
// Returns 20 messages created after the given message ID
|
||||||
|
SinceID ID `json:"since_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q IndexDirectMessagesQuery) String() string {
|
||||||
|
return marshal(&q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexDirectMessagesResponse contains the count and set of
|
||||||
|
// messages returned by the IndexDirectMessages API request
|
||||||
|
type IndexDirectMessagesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Messages []*Message `json:"direct_messages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r IndexDirectMessagesResponse) String() string {
|
||||||
|
return marshal(&r)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IndexDirectMessages -
|
||||||
|
|
||||||
|
Fetch direct messages between two users.
|
||||||
|
|
||||||
|
DMs are returned in groups of 20, ordered by created_at
|
||||||
|
descending.
|
||||||
|
|
||||||
|
If no messages are found (e.g. when filtering with since_id) we
|
||||||
|
return code 304.
|
||||||
|
|
||||||
|
Note that for historical reasons, likes are returned as an array
|
||||||
|
of user ids in the favorited_by key.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
otherUserID - required, ID(string); the other participant in the conversation.
|
||||||
|
See IndexDirectMessagesQuery
|
||||||
|
*/
|
||||||
|
func (c *Client) IndexDirectMessages(ctx context.Context, otherUserID string, req *IndexDirectMessagesQuery, authToken string) (IndexDirectMessagesResponse, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+indexDirectMessagesEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return IndexDirectMessagesResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := httpReq.URL.Query()
|
||||||
|
query.Set("other_user_id", otherUserID)
|
||||||
|
if req != nil {
|
||||||
|
if req.BeforeID != "" {
|
||||||
|
query.Add("before_ID", req.BeforeID.String())
|
||||||
|
}
|
||||||
|
if req.SinceID != "" {
|
||||||
|
query.Add("since_id", req.SinceID.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpReq.URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp IndexDirectMessagesResponse
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return IndexDirectMessagesResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CreateDirectMessage - Send a DM to another user
|
||||||
|
|
||||||
|
If you want to attach an image, you must first process it
|
||||||
|
through our image service.
|
||||||
|
|
||||||
|
Attachments of type emoji rely on data from emoji PowerUps.
|
||||||
|
|
||||||
|
Clients use a placeholder character in the message text and
|
||||||
|
specify a replacement charmap to substitute emoji characters
|
||||||
|
|
||||||
|
The character map is an array of arrays containing rune data
|
||||||
|
([[{pack_id,offset}],...]).
|
||||||
|
*/
|
||||||
|
func (c *Client) CreateDirectMessage(ctx context.Context, m *Message, authToken string) (*Message, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + createDirectMessageEndpoint)
|
||||||
|
|
||||||
|
m.SourceGUID = uuid.New().String()
|
||||||
|
var data = struct {
|
||||||
|
DirectMessage *Message `json:"direct_message,omitempty"`
|
||||||
|
}{
|
||||||
|
m,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
*Message `json:"direct_message"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Message, nil
|
||||||
|
}
|
181
direct_messages_api_test.go
Normal file
181
direct_messages_api_test.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DirectMessagesAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *DirectMessagesAPISuite) SetupSuite() {
|
||||||
|
s.handler = directMessagesTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DirectMessagesAPISuite) TestDirectMessagesIndex() {
|
||||||
|
resp, err := s.client.IndexDirectMessages(
|
||||||
|
context.Background(),
|
||||||
|
"123",
|
||||||
|
&IndexDirectMessagesQuery{
|
||||||
|
BeforeID: "0123456789",
|
||||||
|
SinceID: "9876543210",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(resp)
|
||||||
|
for _, message := range resp.Messages {
|
||||||
|
s.Assert().NotZero(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DirectMessagesAPISuite) TestDirectMessagesCreate() {
|
||||||
|
message, err := s.client.CreateDirectMessage(
|
||||||
|
context.Background(),
|
||||||
|
&Message{
|
||||||
|
RecipientID: ID("123"),
|
||||||
|
Text: "Test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotNil(message)
|
||||||
|
s.Assert().NotZero(*message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDirectMessagesAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(DirectMessagesAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func directMessagesTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Index
|
||||||
|
router.Path("/direct_messages").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexDirectMessages").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"count": 123,
|
||||||
|
"direct_messages": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"recipient_id": "20",
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"favorited_by": [
|
||||||
|
"101"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create
|
||||||
|
router.Path("/direct_messages").
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateDirectMessage").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"message": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"recipient_id": "20",
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"favorited_by": [
|
||||||
|
"101"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
50
examples/group_messages/main.go
Normal file
50
examples/group_messages/main.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is not a real token. Please find yours by logging
|
||||||
|
// into the GroupMe development website: https://dev.groupme.com/
|
||||||
|
const authorizationToken = "0123456789ABCDEF"
|
||||||
|
|
||||||
|
// A short program that gets the gets the first 5 groups
|
||||||
|
// the user is part of, and then the first 10 messages of
|
||||||
|
// the first group in that list
|
||||||
|
func main() {
|
||||||
|
// Create a new client with your auth token
|
||||||
|
client := groupme.NewClient(authorizationToken)
|
||||||
|
|
||||||
|
// Get the groups your user is part of
|
||||||
|
groups, err := client.IndexGroups(
|
||||||
|
context.Background(),
|
||||||
|
&groupme.GroupsQuery{
|
||||||
|
Page: 0,
|
||||||
|
PerPage: 5,
|
||||||
|
Omit: "memberships",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(groups)
|
||||||
|
|
||||||
|
// Get first 10 messages of the first group
|
||||||
|
if len(groups) == 0 {
|
||||||
|
fmt.Println("No groups")
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err := client.IndexMessages(context.Background(), groups[0].ID, &groupme.IndexMessagesQuery{
|
||||||
|
Limit: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(messages)
|
||||||
|
}
|
19
examples/post_bot_message/main.go
Normal file
19
examples/post_bot_message/main.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is not a real Bot ID. Please find yours by logging
|
||||||
|
// into the GroupMe development website: https://dev.groupme.com/bots
|
||||||
|
const botID = "0123456789ABCDEF"
|
||||||
|
|
||||||
|
// A short program that gets the gets the first 5 groups
|
||||||
|
// the user is part of, and then the first 10 messages of
|
||||||
|
// the first group in that list
|
||||||
|
func main() {
|
||||||
|
// Create a new client with your auth token
|
||||||
|
client := groupme.NewClient("")
|
||||||
|
fmt.Println(client.PostBotMessage(context.Background(), botID, "Your message here!", nil))
|
||||||
|
}
|
177
examples/real_time_updates/main.go
Normal file
177
examples/real_time_updates/main.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gitea.watsonlabs.net/watsonb8/groupme-lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is not a real token. Please find yours by logging
|
||||||
|
// into the GroupMe development website: https://dev.groupme.com/
|
||||||
|
|
||||||
|
var authorizationToken = "ASD"
|
||||||
|
var authorizationToken2 = "ASDF"
|
||||||
|
|
||||||
|
// A short program that subscribes to 2 groups and 2 direct chats
|
||||||
|
// and prints out all recognized events in those
|
||||||
|
func main() {
|
||||||
|
//create push subscription and start listening
|
||||||
|
p := groupme.NewPushSubscription(context.Background())
|
||||||
|
err := p.Connect(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new client with your auth token
|
||||||
|
client := groupme.NewClient()
|
||||||
|
User, _ := client.MyUser(context.Background(), authorizationToken)
|
||||||
|
User2, _ := client.MyUser(context.Background(), authorizationToken2)
|
||||||
|
|
||||||
|
//handles (in this case prints) all messages
|
||||||
|
p.AddFullHandler(Handler{User: User}, authorizationToken)
|
||||||
|
p.AddHandler(Handler{User: User2}, authorizationToken2)
|
||||||
|
|
||||||
|
//Subscribe to get messages and events for the specific user
|
||||||
|
p.SubscribeToUser(context.Background(), User.ID, authorizationToken)
|
||||||
|
p.SubscribeToUser(context.Background(), User2.ID, authorizationToken2)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the groups your user is part of
|
||||||
|
groups, err := client.IndexGroups(
|
||||||
|
context.Background(),
|
||||||
|
&groupme.GroupsQuery{
|
||||||
|
Page: 0,
|
||||||
|
PerPage: 2,
|
||||||
|
Omit: "memberships",
|
||||||
|
}, authorizationToken)
|
||||||
|
|
||||||
|
groups2, err := client.IndexGroups(
|
||||||
|
context.Background(),
|
||||||
|
&groupme.GroupsQuery{
|
||||||
|
Page: 0,
|
||||||
|
PerPage: 2,
|
||||||
|
Omit: "memberships",
|
||||||
|
}, authorizationToken2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Subscribe to those groups
|
||||||
|
for _, j := range groups {
|
||||||
|
err = p.SubscribeToGroup(context.TODO(), j.ID, authorizationToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range groups2 {
|
||||||
|
err = p.SubscribeToGroup(context.TODO(), j.ID, authorizationToken2)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//get chats your user is part of
|
||||||
|
chats, err := client.IndexChats(context.Background(),
|
||||||
|
&groupme.IndexChatsQuery{
|
||||||
|
Page: 0,
|
||||||
|
PerPage: 2,
|
||||||
|
}, authorizationToken)
|
||||||
|
chats2, err := client.IndexChats(context.Background(),
|
||||||
|
&groupme.IndexChatsQuery{
|
||||||
|
Page: 0,
|
||||||
|
PerPage: 2,
|
||||||
|
}, authorizationToken2)
|
||||||
|
//subscribe to all those chats
|
||||||
|
for _, j := range chats {
|
||||||
|
go func() {
|
||||||
|
err := p.SubscribeToDM(context.TODO(), j.LastMessage.ConversationID, authorizationToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range chats2 {
|
||||||
|
go func() {
|
||||||
|
err := p.SubscribeToDM(context.TODO(), j.LastMessage.ConversationID, authorizationToken2)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//blocking
|
||||||
|
<-make(chan (struct{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Following example handlers print out all data
|
||||||
|
type Handler struct {
|
||||||
|
User *groupme.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleError(e error) {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleTextMessage(msg groupme.Message) {
|
||||||
|
fmt.Println(msg.Text, msg.Name, msg.Attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleJoin(group groupme.ID) {
|
||||||
|
fmt.Println("User joined group with id", group.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleLike(msg groupme.Message) {
|
||||||
|
fmt.Println(msg.ID, "liked by", msg.FavoritedBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandlerMembership(i groupme.ID) {
|
||||||
|
fmt.Println("Membership event on", i.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleGroupTopic(group groupme.ID, newTopic string) {
|
||||||
|
fmt.Println(group.String(), "has new topic of", newTopic)
|
||||||
|
}
|
||||||
|
func (h Handler) HandleGroupName(group groupme.ID, newName string) {
|
||||||
|
fmt.Println(group.String(), "has new name of", newName)
|
||||||
|
}
|
||||||
|
func (h Handler) HandleGroupAvatar(group groupme.ID, newAvatar string) {
|
||||||
|
fmt.Println(group.String(), "has new avatar url of", newAvatar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleLikeIcon(group groupme.ID, PackID, PackIndex int, Type string) {
|
||||||
|
//Not sure how to use without groupme icon packs
|
||||||
|
if len(Type) == 0 {
|
||||||
|
fmt.Println("Default like icon set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(group.String(), "has new like icon of", PackID, PackIndex, Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleNewNickname(group groupme.ID, user groupme.ID, newName string) {
|
||||||
|
fmt.Printf("In group %s, user %s has new nickname %s\n", group.String(), user.String(), newName)
|
||||||
|
}
|
||||||
|
func (h Handler) HandleNewAvatarInGroup(group groupme.ID, user groupme.ID, avatarURL string) {
|
||||||
|
if avatarURL == "" {
|
||||||
|
//get default avatar
|
||||||
|
avatarURL = h.User.ImageURL
|
||||||
|
}
|
||||||
|
fmt.Printf("In group %s, user %s has new avatar with url %s\n", group.String(), user.String(), avatarURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) HandleMembers(group groupme.ID, members []groupme.Member, added bool) {
|
||||||
|
action := "removed"
|
||||||
|
if added {
|
||||||
|
action = "added"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("In group %s, users %v %s\n", group.String(), members, action)
|
||||||
|
}
|
17
go.mod
Executable file
17
go.mod
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
module gitea.watsonlabs.net/watsonb8/groupme-lib
|
||||||
|
|
||||||
|
go 1.21.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.5-0.20230926180210-b375ab3c8c11
|
||||||
|
github.com/google/uuid v1.2.0
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
50
go.sum
Executable file
50
go.sum
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.0-20230919020138-8f0db7048755 h1:FEhNSjSNvZ+nVg5Z3ds6X8ys3qjM+mmyLTSqKhCUHuQ=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.0-20230919020138-8f0db7048755/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.0-20230919151904-5ca9ade6f946 h1:loc70tiaFs1U4sqn+lKMSBlo5OfvVfClYnWjfGLXaSg=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.0-20230919151904-5ca9ade6f946/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.1 h1:MNFmTaTyyKKgrw04dGO9C5ojtm1jIvy8oHYqbj0ECeY=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.1/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.2 h1:tqbgr1vRZ6Wq4W81xBg+FTOywSv3EJpK263SAVXXTco=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.2/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.3 h1:YpaZBIee8Ix6uGm1UoEtBix1dEU1TURChAsJGJ3pVRo=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.3/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.4 h1:SLvwip1DQy13QngVsEgoLtN7T6bS+X6348p6PQhUF2A=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.4/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.5-0.20230926180210-b375ab3c8c11 h1:xJ9eSFyIrDA43UVpbxOD1QkA2jhg+vS+eezFKCDV3Dw=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.5-0.20230926180210-b375ab3c8c11/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.5 h1:9+UHzUuEcLuZ5Gx5S/NTBxYshUhsiQ5M3vzUF8RAKxw=
|
||||||
|
gitea.watsonlabs.net/watsonb8/fayec v0.0.5/go.mod h1:gv8CWMq6dFJQhH30u8bO3u4k2irKlclZktLNYDebQ/0=
|
||||||
|
github.com/autogrowsystems/wray v0.0.0-20160519030252-f36984f6648c/go.mod h1:druJ8QMeBCUmwJ7ZSFowx77dWxEWF3SYlQlsqZaLZQg=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/karmanyaahm/wray v0.0.0-20210303233435-756d58657c14 h1:NrATjZKvkY+ojL8FXTWa3fQ+wihFrAxLNE6T+wOkIcY=
|
||||||
|
github.com/karmanyaahm/wray v0.0.0-20210303233435-756d58657c14/go.mod h1:ysD86MIEevmAkdfdg5s6Qt3I07RN6fvMAyna7jCGG2o=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
419
groups_api.go
Executable file
419
groups_api.go
Executable file
@ -0,0 +1,419 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#groups
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
groupsEndpointRoot = "/groups"
|
||||||
|
groupEndpointRoot = "/groups/%s"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
indexGroupsEndpoint = groupsEndpointRoot // GET
|
||||||
|
formerGroupsEndpoint = groupsEndpointRoot + "/former" // GET
|
||||||
|
showGroupEndpoint = groupEndpointRoot // GET
|
||||||
|
createGroupEndpoint = groupsEndpointRoot // POST
|
||||||
|
updateGroupEndpoint = groupEndpointRoot + "/update" // POST
|
||||||
|
destroyGroupEndpoint = groupEndpointRoot + "/destroy" // POST
|
||||||
|
joinGroupEndpoint = groupEndpointRoot + "/join/%s" // POST
|
||||||
|
rejoinGroupEndpoint = groupsEndpointRoot + "/join" // POST
|
||||||
|
changeGroupOwnerEndpoint = groupsEndpointRoot + "/change_owners" // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// Common Request Parameters ////////*/
|
||||||
|
|
||||||
|
// GroupSettings is the settings for a group, used by CreateGroup and UpdateGroup
|
||||||
|
type GroupSettings struct {
|
||||||
|
// Required. Primary name of the group. Maximum 140 characters
|
||||||
|
Name string `json:"name"`
|
||||||
|
// A subheading for the group. Maximum 255 characters
|
||||||
|
Description string `json:"description"`
|
||||||
|
// GroupMe Image Service URL
|
||||||
|
ImageURL string `json:"image_url"`
|
||||||
|
// Defaults false. If true, disables notifications for all members.
|
||||||
|
// Documented for use only for UpdateGroup
|
||||||
|
OfficeMode bool `json:"office_mode"`
|
||||||
|
// Defaults false. If true, generates a share URL.
|
||||||
|
// Anyone with the URL can join the group
|
||||||
|
Share bool `json:"share"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gss GroupSettings) String() string {
|
||||||
|
return marshal(&gss)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
/*/// Index ///*/
|
||||||
|
|
||||||
|
// GroupsQuery defines optional URL parameters for IndexGroups
|
||||||
|
type GroupsQuery struct {
|
||||||
|
// Fetch a particular page of results. Defaults to 1.
|
||||||
|
Page int `json:"page"`
|
||||||
|
// Define page size. Defaults to 10.
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
// Comma separated list of data to omit from output.
|
||||||
|
// Currently supported value is only "memberships".
|
||||||
|
// If used then response will contain empty (null) members field.
|
||||||
|
Omit string `json:"omit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q GroupsQuery) String() string {
|
||||||
|
return marshal(&q)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IndexGroups -
|
||||||
|
|
||||||
|
List the authenticated user's active groups.
|
||||||
|
|
||||||
|
The response is paginated, with a default of 10 groups per page.
|
||||||
|
|
||||||
|
Please consider using of omit=memberships parameter. Not including
|
||||||
|
member lists might significantly improve user experience of your
|
||||||
|
app for users who are participating in huge groups.
|
||||||
|
|
||||||
|
Parameters: See GroupsQuery
|
||||||
|
*/
|
||||||
|
func (c *Client) IndexGroups(ctx context.Context, req *GroupsQuery, authToken string) ([]*Group, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+indexGroupsEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
if req != nil {
|
||||||
|
if req.Page != 0 {
|
||||||
|
query.Set("page", strconv.Itoa(req.Page))
|
||||||
|
}
|
||||||
|
if req.PerPage != 0 {
|
||||||
|
query.Set("per_page", strconv.Itoa(req.PerPage))
|
||||||
|
}
|
||||||
|
if req.Omit != "" {
|
||||||
|
query.Set("omit", req.Omit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp []*Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Former ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
FormerGroups -
|
||||||
|
|
||||||
|
List they groups you have left but can rejoin.
|
||||||
|
*/
|
||||||
|
func (c *Client) FormerGroups(ctx context.Context, authToken string) ([]*Group, error) {
|
||||||
|
httpReq, err := http.NewRequest("GET", c.endpointBase+formerGroupsEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Show ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
ShowGroup -
|
||||||
|
|
||||||
|
Loads a specific group.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
*/
|
||||||
|
func (c *Client) ShowGroup(ctx context.Context, groupID ID, authToken string) (*Group, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+showGroupEndpoint, groupID)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("GET", URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Create ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
CreateGroup -
|
||||||
|
|
||||||
|
# Create a new group
|
||||||
|
|
||||||
|
Parameters: See GroupSettings
|
||||||
|
*/
|
||||||
|
func (c *Client) CreateGroup(ctx context.Context, gs GroupSettings, authToken string) (*Group, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + createGroupEndpoint)
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&gs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Update ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateGroup -
|
||||||
|
|
||||||
|
# Update a group after creation
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
See GroupSettings
|
||||||
|
*/
|
||||||
|
func (c *Client) UpdateGroup(ctx context.Context, groupID ID, gs GroupSettings, authToken string) (*Group, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+updateGroupEndpoint, groupID)
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&gs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Destroy ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
DestroyGroup -
|
||||||
|
|
||||||
|
# Disband a group
|
||||||
|
|
||||||
|
# This action is only available to the group creator
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
*/
|
||||||
|
func (c *Client) DestroyGroup(ctx context.Context, groupID ID, authToken string) error {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+destroyGroupEndpoint, groupID)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Join ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
JoinGroup -
|
||||||
|
|
||||||
|
# Join a shared group
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
shareToken - required, string
|
||||||
|
*/
|
||||||
|
func (c *Client) JoinGroup(ctx context.Context, groupID ID, shareToken string, authToken string) (*Group, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+joinGroupEndpoint, groupID, shareToken)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Rejoin ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
RejoinGroup -
|
||||||
|
|
||||||
|
Rejoin a group. Only works if you previously removed yourself.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
*/
|
||||||
|
func (c *Client) RejoinGroup(ctx context.Context, groupID ID, authToken string) (*Group, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + rejoinGroupEndpoint)
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
GroupID ID `json:"group_id"`
|
||||||
|
}{
|
||||||
|
groupID,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Group
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Change Owner ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
ChangeGroupOwner - Change owner of requested groups.
|
||||||
|
|
||||||
|
This action is only available to the group creator.
|
||||||
|
|
||||||
|
Response is a result object which contain status field,
|
||||||
|
the result of change owner action for the request
|
||||||
|
|
||||||
|
Parameters: See ChangeOwnerRequest
|
||||||
|
*/
|
||||||
|
func (c *Client) ChangeGroupOwner(ctx context.Context, reqs ChangeOwnerRequest, authToken string) (ChangeOwnerResult, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + changeGroupOwnerEndpoint)
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
Requests []ChangeOwnerRequest `json:"requests"`
|
||||||
|
}{
|
||||||
|
[]ChangeOwnerRequest{reqs},
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return ChangeOwnerResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return ChangeOwnerResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Results []ChangeOwnerResult `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return ChangeOwnerResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Results) < 1 {
|
||||||
|
return ChangeOwnerResult{}, errors.New("failed to parse results")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Results[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type changeOwnerStatusCode string
|
||||||
|
|
||||||
|
// Change owner Status Codes
|
||||||
|
const (
|
||||||
|
ChangeOwnerOk changeOwnerStatusCode = "200"
|
||||||
|
ChangeOwnerRequesterNewOwner changeOwnerStatusCode = "400"
|
||||||
|
ChangeOwnerNotOwner changeOwnerStatusCode = "403"
|
||||||
|
ChangeOwnerBadGroupOrOwner changeOwnerStatusCode = "404"
|
||||||
|
ChangeOwnerBadRequest changeOwnerStatusCode = "405"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the description of the status code according to GroupMe
|
||||||
|
func (c changeOwnerStatusCode) String() string {
|
||||||
|
return map[changeOwnerStatusCode]string{
|
||||||
|
ChangeOwnerOk: "success",
|
||||||
|
ChangeOwnerRequesterNewOwner: "requester is also a new owner",
|
||||||
|
ChangeOwnerNotOwner: "requester is not the owner of the group",
|
||||||
|
ChangeOwnerBadGroupOrOwner: "group or new owner not found or new owner is not member of the group",
|
||||||
|
ChangeOwnerBadRequest: "request object is missing required field or any of the required fields is not an ID",
|
||||||
|
}[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeOwnerRequest defines the new owner of a group
|
||||||
|
type ChangeOwnerRequest struct {
|
||||||
|
// Required
|
||||||
|
GroupID string `json:"group_id"`
|
||||||
|
// Required. UserId of the new owner of the group
|
||||||
|
// who must be an active member of the group
|
||||||
|
OwnerID string `json:"owner_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ChangeOwnerRequest) String() string {
|
||||||
|
return marshal(&r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeOwnerResult holds the status of the group owner change
|
||||||
|
type ChangeOwnerResult struct {
|
||||||
|
GroupID string `json:"group_id"`
|
||||||
|
// UserId of the new owner of the group who is
|
||||||
|
// an active member of the group
|
||||||
|
OwnerID string `json:"owner_id"`
|
||||||
|
Status changeOwnerStatusCode `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ChangeOwnerResult) String() string {
|
||||||
|
return marshal(&r)
|
||||||
|
}
|
700
groups_api_test.go
Executable file
700
groups_api_test.go
Executable file
@ -0,0 +1,700 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupsAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) SetupSuite() {
|
||||||
|
s.handler = groupsTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsIndex() {
|
||||||
|
groups, err := s.client.IndexGroups(
|
||||||
|
context.Background(),
|
||||||
|
&GroupsQuery{
|
||||||
|
Page: 5,
|
||||||
|
PerPage: 20,
|
||||||
|
Omit: "memberships",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(groups)
|
||||||
|
for _, group := range groups {
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsFormer() {
|
||||||
|
groups, err := s.client.FormerGroups(context.Background())
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(groups)
|
||||||
|
for _, group := range groups {
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsShow() {
|
||||||
|
group, err := s.client.ShowGroup(context.Background(), "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsCreate() {
|
||||||
|
group, err := s.client.CreateGroup(
|
||||||
|
context.Background(),
|
||||||
|
GroupSettings{
|
||||||
|
"Test",
|
||||||
|
"This is a test group",
|
||||||
|
"www.blank.com/image",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsUpdate() {
|
||||||
|
group, err := s.client.UpdateGroup(context.Background(), "1", GroupSettings{
|
||||||
|
"Test",
|
||||||
|
"This is a test group",
|
||||||
|
"www.blank.com/image",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsDestroy() {
|
||||||
|
err := s.client.DestroyGroup(context.Background(), "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsJoin() {
|
||||||
|
group, err := s.client.JoinGroup(context.Background(), "1", "please")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsRejoin() {
|
||||||
|
group, err := s.client.RejoinGroup(context.Background(), "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GroupsAPISuite) TestGroupsChangeOwner() {
|
||||||
|
result, err := s.client.ChangeGroupOwner(
|
||||||
|
context.Background(),
|
||||||
|
ChangeOwnerRequest{
|
||||||
|
"1",
|
||||||
|
"123",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(result)
|
||||||
|
}
|
||||||
|
func TestGroupsAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(GroupsAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////// Test Groups Router ////////*/
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func groupsTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Index
|
||||||
|
router.Path("/groups").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexGroups").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Former
|
||||||
|
router.Path("/groups/former").
|
||||||
|
Methods("GET").
|
||||||
|
Name("FormerGroups").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show
|
||||||
|
router.Path("/groups/{id:[0-9]+}").
|
||||||
|
Methods("GET").
|
||||||
|
Name("ShowGroup").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create
|
||||||
|
router.Path("/groups").
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateGroup").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update
|
||||||
|
router.Path("/groups/{id:[0-9]+}/update").
|
||||||
|
Methods("POST").
|
||||||
|
Name("UpdateGroup").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Destroy
|
||||||
|
router.Path("/groups/{id:[0-9]+}/destroy").
|
||||||
|
Methods("POST").
|
||||||
|
Name("DestroyGroup").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Join
|
||||||
|
router.Path("/groups/{id:[0-9]+}/join/{share_token}").
|
||||||
|
Methods("POST").
|
||||||
|
Name("JoinGroup").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"group": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Rejoin
|
||||||
|
router.Path("/groups/join").
|
||||||
|
Methods("POST").
|
||||||
|
Name("RejoinGroup").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"name": "Family",
|
||||||
|
"type": "private",
|
||||||
|
"description": "Coolest Family Ever",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"creator_user_id": "1234567890",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"nickname": "Jane",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/123456789"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_url": "https://groupme.com/join_group/1234567890/SHARE_TOKEN",
|
||||||
|
"messages": {
|
||||||
|
"count": 100,
|
||||||
|
"last_message_id": "1234567890",
|
||||||
|
"last_message_created_at": 1302623328,
|
||||||
|
"preview": {
|
||||||
|
"nickname": "Jane",
|
||||||
|
"text": "Hello world",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Change Owner
|
||||||
|
router.Path("/groups/change_owners").
|
||||||
|
Methods("POST").
|
||||||
|
Name("ChangeGroupOwner").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"owner_id": "1234567890",
|
||||||
|
"status": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"owner_id": "1234567890",
|
||||||
|
"status": "400"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
233
json.go
Executable file
233
json.go
Executable file
@ -0,0 +1,233 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Meta is the error type returned in the GroupMe response.
|
||||||
|
// Meant for clients that can't read HTTP status codes
|
||||||
|
type Meta struct {
|
||||||
|
Code HTTPStatusCode `json:"code,omitempty"`
|
||||||
|
Errors []string `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the code and the error list as a string.
|
||||||
|
// Satisfies the error interface
|
||||||
|
func (m Meta) Error() string {
|
||||||
|
return fmt.Sprintf("Error Code %d: %v", m.Code, m.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group is a GroupMe group, returned in JSON API responses
|
||||||
|
type Group struct {
|
||||||
|
ID ID `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
// Type of group (private|public)
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
CreatorUserID ID `json:"creator_user_id,omitempty"`
|
||||||
|
CreatedAt Timestamp `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt Timestamp `json:"updated_at,omitempty"`
|
||||||
|
Members []*Member `json:"members,omitempty"`
|
||||||
|
ShareURL string `json:"share_url,omitempty"`
|
||||||
|
Messages GroupMessages `json:"messages,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupMessages is a Group field, only returned in Group JSON API responses
|
||||||
|
type GroupMessages struct {
|
||||||
|
Count uint `json:"count,omitempty"`
|
||||||
|
LastMessageID ID `json:"last_message_id,omitempty"`
|
||||||
|
LastMessageCreatedAt Timestamp `json:"last_message_created_at,omitempty"`
|
||||||
|
Preview MessagePreview `json:"preview,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessagePreview is a GroupMessages field, only returned in Group JSON API responses.
|
||||||
|
// Abbreviated form of Message type
|
||||||
|
type MessagePreview struct {
|
||||||
|
Nickname string `json:"nickname,omitempty"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
Attachments []*Attachment `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemberByUserID gets the group member by their UserID,
|
||||||
|
// nil if no member matches
|
||||||
|
func (g *Group) GetMemberByUserID(userID ID) *Member {
|
||||||
|
for _, member := range g.Members {
|
||||||
|
if member.UserID == userID {
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemberByNickname gets the group member by their Nickname,
|
||||||
|
// nil if no member matches
|
||||||
|
func (g *Group) GetMemberByNickname(nickname string) *Member {
|
||||||
|
for _, member := range g.Members {
|
||||||
|
if member.Nickname == nickname {
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) String() string {
|
||||||
|
return marshal(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member is a GroupMe group member, returned in JSON API responses
|
||||||
|
type Member struct {
|
||||||
|
ID ID `json:"id,omitempty"`
|
||||||
|
UserID ID `json:"user_id,omitempty"`
|
||||||
|
Nickname string `json:"nickname,omitempty"`
|
||||||
|
Muted bool `json:"muted,omitempty"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
AutoKicked bool `json:"autokicked,omitempty"`
|
||||||
|
AppInstalled bool `json:"app_installed,omitempty"`
|
||||||
|
GUID string `json:"guid,omitempty"`
|
||||||
|
PhoneNumber string `json:"phone_number,omitempty"` // Only used when searching for the member to add to a group.
|
||||||
|
Email string `json:"email,omitempty"` // Only used when searching for the member to add to a group.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Member) String() string {
|
||||||
|
return marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message is a GroupMe group message, returned in JSON API responses
|
||||||
|
type Message struct {
|
||||||
|
ID ID `json:"id,omitempty"`
|
||||||
|
SourceGUID string `json:"source_guid,omitempty"`
|
||||||
|
CreatedAt Timestamp `json:"created_at,omitempty"`
|
||||||
|
GroupID ID `json:"group_id,omitempty"`
|
||||||
|
UserID ID `json:"user_id,omitempty"`
|
||||||
|
BotID ID `json:"bot_id,omitempty"`
|
||||||
|
SenderID ID `json:"sender_id,omitempty"`
|
||||||
|
SenderType senderType `json:"sender_type,omitempty"`
|
||||||
|
System bool `json:"system,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
RecipientID ID `json:"recipient_id,omitempty"`
|
||||||
|
//ChatID - over push ConversationID seems to be called ChatID
|
||||||
|
ChatID ID `json:"chat_id,omitempty"`
|
||||||
|
ConversationID ID `json:"conversation_id,omitempty"`
|
||||||
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
|
// Maximum length of 1000 characters
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
// Must be an image service URL (i.groupme.com)
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
FavoritedBy []string `json:"favorited_by,omitempty"`
|
||||||
|
Attachments []*Attachment `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) String() string {
|
||||||
|
return marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
type senderType string
|
||||||
|
|
||||||
|
// SenderType constants
|
||||||
|
const (
|
||||||
|
SenderTypeUser senderType = "user"
|
||||||
|
SenderTypeBot senderType = "bot"
|
||||||
|
SenderTypeSystem senderType = "system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attachmentType string
|
||||||
|
|
||||||
|
// AttachmentType constants
|
||||||
|
const (
|
||||||
|
Mentions attachmentType = "mentions"
|
||||||
|
Image attachmentType = "image"
|
||||||
|
Location attachmentType = "location"
|
||||||
|
Emoji attachmentType = "emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attachment is a GroupMe message attachment, returned in JSON API responses
|
||||||
|
type Attachment struct {
|
||||||
|
Type attachmentType `json:"type,omitempty"`
|
||||||
|
Loci [][]int `json:"loci,omitempty"`
|
||||||
|
UserIDs []ID `json:"user_ids,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
FileID string `json:"file_id,omitempty"`
|
||||||
|
VideoPreviewURL string `json:"preview_url,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Latitude string `json:"lat,omitempty"`
|
||||||
|
Longitude string `json:"lng,omitempty"`
|
||||||
|
Placeholder string `json:"placeholder,omitempty"`
|
||||||
|
Charmap [][]int `json:"charmap,omitempty"`
|
||||||
|
ReplyID ID `json:"reply_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attachment) String() string {
|
||||||
|
return marshal(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is a GroupMe user, returned in JSON API responses
|
||||||
|
type User struct {
|
||||||
|
ID ID `json:"id,omitempty"`
|
||||||
|
PhoneNumber PhoneNumber `json:"phone_number,omitempty"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
CreatedAt Timestamp `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt Timestamp `json:"updated_at,omitempty"`
|
||||||
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
SMS bool `json:"sms,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) String() string {
|
||||||
|
return marshal(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat is a GroupMe direct message conversation between two users,
|
||||||
|
// returned in JSON API responses
|
||||||
|
type Chat struct {
|
||||||
|
CreatedAt Timestamp `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt Timestamp `json:"updated_at,omitempty"`
|
||||||
|
LastMessage *Message `json:"last_message,omitempty"`
|
||||||
|
MessagesCount int `json:"messages_count,omitempty"`
|
||||||
|
OtherUser User `json:"other_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) String() string {
|
||||||
|
return marshal(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bot is a GroupMe bot, it is connected to a specific group which it can send messages to
|
||||||
|
type Bot struct {
|
||||||
|
BotID ID `json:"bot_id,omitempty"`
|
||||||
|
GroupID ID `json:"group_id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
|
CallbackURL string `json:"callback_url,omitempty"`
|
||||||
|
DMNotification bool `json:"dm_notification,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) String() string {
|
||||||
|
return marshal(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block is a GroupMe block between two users, direct messages are not allowed
|
||||||
|
type Block struct {
|
||||||
|
UserID ID `json:"user_id,omitempty"`
|
||||||
|
BlockedUserID ID `json:"blocked_user_id,omitempty"`
|
||||||
|
CreatedAT Timestamp `json:"created_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Block) String() string {
|
||||||
|
return marshal(&b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Superficially increases test coverage
|
||||||
|
func marshal(i interface{}) string {
|
||||||
|
bytes, err := json.MarshalIndent(i, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes)
|
||||||
|
}
|
75
json_test.go
Executable file
75
json_test.go
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JSONSuite) TestGroup_GetMemberByUserID_Match() {
|
||||||
|
m := Member{
|
||||||
|
UserID: "123",
|
||||||
|
}
|
||||||
|
|
||||||
|
g := Group{
|
||||||
|
Members: []*Member{&m},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := g.GetMemberByUserID("123")
|
||||||
|
|
||||||
|
s.Require().NotNil(actual)
|
||||||
|
s.Assert().Equal(m, *actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JSONSuite) TestGroup_GetMemberByUserID_NoMatch() {
|
||||||
|
g := Group{
|
||||||
|
Members: []*Member{},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := g.GetMemberByUserID("123")
|
||||||
|
|
||||||
|
s.Require().Nil(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JSONSuite) TestGroup_GetMemberByNickname_Match() {
|
||||||
|
m := Member{
|
||||||
|
Nickname: "Test User",
|
||||||
|
}
|
||||||
|
|
||||||
|
g := Group{
|
||||||
|
Members: []*Member{&m},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := g.GetMemberByNickname("Test User")
|
||||||
|
|
||||||
|
s.Require().NotNil(actual)
|
||||||
|
s.Assert().Equal(m, *actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JSONSuite) TestGroup_GetMemberByNickname_NoMatch() {
|
||||||
|
g := Group{
|
||||||
|
Members: []*Member{},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := g.GetMemberByNickname("Test User")
|
||||||
|
|
||||||
|
s.Require().Nil(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JSONSuite) TestMarshal_NoError() {
|
||||||
|
s.Assert().Equal("{}", marshal(&struct{}{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JSONSuite) TestMarshal_Error() {
|
||||||
|
var c chan struct{}
|
||||||
|
s.Assert().Equal("", marshal(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(JSONSuite))
|
||||||
|
}
|
121
leaderboard_api.go
Normal file
121
leaderboard_api.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#leaderboard
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
leaderboardEndpointRoot = groupEndpointRoot + "/likes"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
indexLeaderboardEndpoint = leaderboardEndpointRoot // GET
|
||||||
|
myLikesLeaderboardEndpoint = leaderboardEndpointRoot + "/mine" // GET
|
||||||
|
myHitsLeaderboardEndpoint = leaderboardEndpointRoot + "/for_me" // GET
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// Index
|
||||||
|
|
||||||
|
type period string
|
||||||
|
|
||||||
|
// Define acceptable period values
|
||||||
|
const (
|
||||||
|
PeriodDay = "day"
|
||||||
|
PeriodWeek = "week"
|
||||||
|
PeriodMonth = "month"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IndexLeaderboard - A list of the liked messages in the group for a given period of
|
||||||
|
// time. Messages are ranked in order of number of likes.
|
||||||
|
func (c *Client) IndexLeaderboard(ctx context.Context, groupID ID, p period, authToken string) ([]*Message, error) {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+indexLeaderboardEndpoint, groupID)
|
||||||
|
httpReq, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("period", string(p))
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Messages []*Message `json:"messages"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// My Likes
|
||||||
|
|
||||||
|
/*
|
||||||
|
MyLikesLeaderboard -
|
||||||
|
|
||||||
|
A list of messages you have liked. Messages are returned in
|
||||||
|
reverse chrono-order. Note that the payload includes a liked_at
|
||||||
|
timestamp in ISO-8601 format.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
*/
|
||||||
|
func (c *Client) MyLikesLeaderboard(ctx context.Context, groupID ID, authToken string) ([]*Message, error) {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+myLikesLeaderboardEndpoint, groupID)
|
||||||
|
httpReq, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Messages []*Message `json:"messages"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// My Hits
|
||||||
|
|
||||||
|
/*
|
||||||
|
MyHitsLeaderboard -
|
||||||
|
|
||||||
|
A list of messages you have liked. Messages are returned in
|
||||||
|
reverse chrono-order. Note that the payload includes a liked_at
|
||||||
|
timestamp in ISO-8601 format.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
*/
|
||||||
|
func (c *Client) MyHitsLeaderboard(ctx context.Context, groupID ID, authToken string) ([]*Message, error) {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+myHitsLeaderboardEndpoint, groupID)
|
||||||
|
httpReq, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Messages []*Message `json:"messages"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Messages, nil
|
||||||
|
}
|
313
leaderboard_api_test.go
Normal file
313
leaderboard_api_test.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaderboardAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *LeaderboardAPISuite) SetupSuite() {
|
||||||
|
s.handler = leaderboardTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LeaderboardAPISuite) TestLeaderboardIndex() {
|
||||||
|
messages, err := s.client.IndexLeaderboard(context.Background(), "1", PeriodDay)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(messages)
|
||||||
|
for _, message := range messages {
|
||||||
|
s.Assert().NotZero(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LeaderboardAPISuite) TestLeaderboardMyLikes() {
|
||||||
|
messages, err := s.client.MyLikesLeaderboard(context.Background(), "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(messages)
|
||||||
|
for _, message := range messages {
|
||||||
|
s.Assert().NotZero(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LeaderboardAPISuite) TestLeaderboardMyHits() {
|
||||||
|
messages, err := s.client.MyHitsLeaderboard(context.Background(), "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(messages)
|
||||||
|
for _, message := range messages {
|
||||||
|
s.Assert().NotZero(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLeaderboardAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(LeaderboardAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func leaderboardTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Index
|
||||||
|
router.Path("/groups/{id:[0-9]+}/likes").
|
||||||
|
Queries("period", "{period:day|week|month}").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexLeaderboard").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"system": true,
|
||||||
|
"favorited_by": [
|
||||||
|
"101",
|
||||||
|
"66",
|
||||||
|
"1234567890"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"system": true,
|
||||||
|
"favorited_by": [
|
||||||
|
"1",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// My Likes
|
||||||
|
router.Path("/groups/{id:[0-9]+}/likes/mine").
|
||||||
|
Methods("GET").
|
||||||
|
Name("MyLikesLeaderboard").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"system": true,
|
||||||
|
"favorited_by": [
|
||||||
|
"101",
|
||||||
|
"66",
|
||||||
|
"1234567890"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"liked_at": "2014-05-08T18:30:31.6617Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// My Hits
|
||||||
|
router.Path("/groups/{id:[0-9]+}/likes/for_me").
|
||||||
|
Methods("GET").
|
||||||
|
Name("MyHitsLeaderboard").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"system": true,
|
||||||
|
"favorited_by": [
|
||||||
|
"101",
|
||||||
|
"66",
|
||||||
|
"1234567890"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
47
likes_api.go
Normal file
47
likes_api.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#likes
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
likesEndpointRoot = "/messages/%s/%s"
|
||||||
|
|
||||||
|
createLikeEndpoint = likesEndpointRoot + "/like" // POST
|
||||||
|
destroyLikeEndpoint = likesEndpointRoot + "/unlike" // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// Create
|
||||||
|
|
||||||
|
// CreateLike - Like a message.
|
||||||
|
func (c *Client) CreateLike(ctx context.Context, conversationID, messageID ID, authToken string) error {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+createLikeEndpoint, conversationID, messageID)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyLike - Unlike a message.
|
||||||
|
func (c *Client) DestroyLike(ctx context.Context, conversationID, messageID ID, authToken string) error {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+destroyLikeEndpoint, conversationID, messageID)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
}
|
56
likes_api_test.go
Normal file
56
likes_api_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LikesAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *LikesAPISuite) SetupSuite() {
|
||||||
|
s.handler = likesTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LikesAPISuite) TestLikesCreate() {
|
||||||
|
err := s.client.CreateLike(context.Background(), "1", "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LikesAPISuite) TestLikesDestroy() {
|
||||||
|
err := s.client.DestroyLike(context.Background(), "1", "1")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLikesAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(LikesAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func likesTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Create
|
||||||
|
router.Path(`/messages/{conversation_id}/{message_id}/like`).
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateLike").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Destroy
|
||||||
|
router.Path(`/messages/{conversation_id}/{message_id}/unlike`).
|
||||||
|
Methods("POST").
|
||||||
|
Name("DestroyLike").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
88
main_test.go
Executable file
88
main_test.go
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// Base API Suite ////////*/
|
||||||
|
type APISuite struct {
|
||||||
|
// Base attributes
|
||||||
|
suite.Suite
|
||||||
|
client *Client
|
||||||
|
server *http.Server
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Overridden by child Suite
|
||||||
|
addr string
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APISuite) setupSuite() {
|
||||||
|
s.addr = "localhost:" + s.generatePort()
|
||||||
|
|
||||||
|
s.client = NewClient("")
|
||||||
|
s.client.endpointBase = "http://" + s.addr
|
||||||
|
|
||||||
|
s.server = s.startServer(s.addr, s.handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APISuite) TearDownSuite() {
|
||||||
|
s.client.Close()
|
||||||
|
s.server.Close()
|
||||||
|
s.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Start Server ///*/
|
||||||
|
func (s *APISuite) startServer(addr string, handler http.Handler) *http.Server {
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: handler,
|
||||||
|
ErrorLog: log.New(os.Stdout, "SERVER", log.Ltime),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
if err := server.ListenAndServe(); err.Error() != "http: Server closed" {
|
||||||
|
s.Assert().NoError(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait until server has started listening
|
||||||
|
url := fmt.Sprintf("http://%s", addr)
|
||||||
|
// nolint // url is meant to be variable
|
||||||
|
for _, err := http.Get(url); err != nil; _, err = http.Get(url) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Generate Ephemeral Port ///*/
|
||||||
|
const (
|
||||||
|
portMin = 49152
|
||||||
|
portMax = 65535
|
||||||
|
portRange = portMax - portMin
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *APISuite) generatePort() string {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
// nolint // weak random generator is ok for creating port number in a test
|
||||||
|
return strconv.Itoa((rand.Intn(portRange) + portMin))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////// Test Main ////////*/
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
185
members_api.go
Normal file
185
members_api.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#members
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
membersEndpointRoot = groupEndpointRoot + "/members"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
addMembersEndpoint = membersEndpointRoot + "/add" // POST
|
||||||
|
addMembersResultsEndpoint = membersEndpointRoot + "/results/%s" // GET
|
||||||
|
removeMemberEndpoint = membersEndpointRoot + "/%s/remove" // POST
|
||||||
|
updateMemberEndpoint = groupEndpointRoot + "/memberships/update" // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*/// Add ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddMembers -
|
||||||
|
|
||||||
|
Add members to a group.
|
||||||
|
|
||||||
|
Multiple members can be added in a single request, and results
|
||||||
|
are fetchedwith a separate call (since memberships are processed
|
||||||
|
asynchronously). The response includes a results_id that's used
|
||||||
|
in the results request.
|
||||||
|
|
||||||
|
In order to correlate request params with resulting memberships,
|
||||||
|
GUIDs can be added to the members parameters. These GUIDs will
|
||||||
|
be reflected in the membership JSON objects.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
See Member.
|
||||||
|
Nickname - required
|
||||||
|
One of the following identifiers must be used:
|
||||||
|
UserID - ID(string)
|
||||||
|
PhoneNumber - PhoneNumber(string)
|
||||||
|
Email - string
|
||||||
|
*/
|
||||||
|
func (c *Client) AddMembers(ctx context.Context, groupID ID, authToken string, members ...*Member) (string, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+addMembersEndpoint, groupID)
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
Members []*Member `json:"members"`
|
||||||
|
}{
|
||||||
|
members,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
ResultsID string `json:"results_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ResultsID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Results ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddMembersResults -
|
||||||
|
Get the membership results from an add call.
|
||||||
|
|
||||||
|
Successfully created memberships will be returned, including
|
||||||
|
any GUIDs that were sent up in the add request. If GUIDs were
|
||||||
|
absent, they are filled in automatically. Failed memberships
|
||||||
|
and invites are omitted.
|
||||||
|
|
||||||
|
Keep in mind that results are temporary -- they will only be
|
||||||
|
available for 1 hour after the add request.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
resultID - required, string
|
||||||
|
*/
|
||||||
|
func (c *Client) AddMembersResults(ctx context.Context, groupID ID, resultID string, authToken string) ([]*Member, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+addMembersResultsEndpoint, groupID, resultID)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("GET", URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Members []*Member `json:"members"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Members, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Remove ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
RemoveMember -
|
||||||
|
|
||||||
|
Remove a member (or yourself) from a group.
|
||||||
|
|
||||||
|
Note: The creator of the group cannot be removed or exit.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
membershipID - required, ID(string). Not the same as userID
|
||||||
|
*/
|
||||||
|
func (c *Client) RemoveMember(ctx context.Context, groupID, membershipID ID, authToken string) error {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+removeMemberEndpoint, groupID, membershipID)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Update ///*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateMember -
|
||||||
|
|
||||||
|
Update your nickname in a group. The nickname must be
|
||||||
|
between 1 and 50 characters.
|
||||||
|
*/
|
||||||
|
func (c *Client) UpdateMember(ctx context.Context, groupID ID, nickname string, authToken string) (*Member, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+updateMemberEndpoint, groupID)
|
||||||
|
|
||||||
|
type Nickname struct {
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
}
|
||||||
|
var data = struct {
|
||||||
|
Membership Nickname `json:"membership"`
|
||||||
|
}{
|
||||||
|
Nickname{nickname},
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Member
|
||||||
|
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
140
members_api_test.go
Normal file
140
members_api_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MembersAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *MembersAPISuite) SetupSuite() {
|
||||||
|
s.handler = membersTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MembersAPISuite) TestMembersAdd() {
|
||||||
|
_, err := s.client.AddMembers(
|
||||||
|
context.Background(),
|
||||||
|
"1",
|
||||||
|
&Member{Nickname: "test"},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MembersAPISuite) TestMembersResults() {
|
||||||
|
_, err := s.client.AddMembersResults(context.Background(), "1", "123")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MembersAPISuite) TestMembersRemove() {
|
||||||
|
err := s.client.RemoveMember(context.Background(), "1", "123")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MembersAPISuite) TestMembersUpdate() {
|
||||||
|
_, err := s.client.UpdateMember(context.Background(), "1", "nickname")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMembersAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MembersAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func membersTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Add
|
||||||
|
router.Path("/groups/{id:[0-9]+}/members/add").
|
||||||
|
Methods("POST").
|
||||||
|
Name("AddMembers").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(202)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"results_id": "GUID"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 202,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Results
|
||||||
|
router.Path("/groups/{id:[0-9]+}/members/results/{result_id}").
|
||||||
|
Methods("GET").
|
||||||
|
Name("AddMembersResults").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"id": "1000",
|
||||||
|
"user_id": "10000",
|
||||||
|
"nickname": "John",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/AVATAR",
|
||||||
|
"autokicked": false,
|
||||||
|
"app_installed": true,
|
||||||
|
"guid": "GUID-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2000",
|
||||||
|
"user_id": "20000",
|
||||||
|
"nickname": "Anne",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "https://i.groupme.com/AVATAR",
|
||||||
|
"autokicked": false,
|
||||||
|
"app_installed": true,
|
||||||
|
"guid": "GUID-2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove
|
||||||
|
router.Path("/groups/{id:[0-9]+}/members/{membership_id}/remove").
|
||||||
|
Methods("POST").
|
||||||
|
Name("RemoveMember").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update
|
||||||
|
router.Path("/groups/{id:[0-9]+}/memberships/update").
|
||||||
|
Methods("POST").
|
||||||
|
Name("UpdateMember").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "MEMBERSHIP ID",
|
||||||
|
"user_id": "USER ID",
|
||||||
|
"nickname": "NEW NICKNAME",
|
||||||
|
"muted": false,
|
||||||
|
"image_url": "AVATAR URL",
|
||||||
|
"autokicked": false,
|
||||||
|
"app_installed": true
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
157
messages_api.go
Normal file
157
messages_api.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#messages
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
messagesEndpointRoot = groupEndpointRoot + "/messages"
|
||||||
|
|
||||||
|
indexMessagesEndpoint = messagesEndpointRoot // GET
|
||||||
|
createMessagesEndpoint = messagesEndpointRoot // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
// IndexMessagesQuery defines the optional URL parameters for IndexMessages
|
||||||
|
type IndexMessagesQuery struct {
|
||||||
|
// Returns messages created before the given message ID
|
||||||
|
BeforeID ID
|
||||||
|
// Returns most recent messages created after the given message ID
|
||||||
|
SinceID ID
|
||||||
|
// Returns messages created immediately after the given message ID
|
||||||
|
AfterID ID
|
||||||
|
// Number of messages returned. Default is 20. Max is 100.
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q IndexMessagesQuery) String() string {
|
||||||
|
return marshal(&q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexMessagesResponse contains the count and set of
|
||||||
|
// messages returned by the IndexMessages API request
|
||||||
|
type IndexMessagesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Messages []*Message `json:"messages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r IndexMessagesResponse) String() string {
|
||||||
|
return marshal(&r)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IndexMessages - Retrieves messages for a group.
|
||||||
|
By default, messages are returned in groups of 20, ordered by
|
||||||
|
created_at descending. This can be raised or lowered by passing
|
||||||
|
a limit parameter, up to a maximum of 100 messages.
|
||||||
|
Messages can be scanned by providing a message ID as either the
|
||||||
|
before_id, since_id, or after_id parameter. If before_id is
|
||||||
|
provided, then messages immediately preceding the given message
|
||||||
|
will be returned, in descending order. This can be used to
|
||||||
|
continually page back through a group's messages.
|
||||||
|
The after_id parameter will return messages that immediately
|
||||||
|
follow a given message, this time in ascending order (which
|
||||||
|
makes it easy to pick off the last result for continued
|
||||||
|
pagination).
|
||||||
|
Finally, the since_id parameter also returns messages created
|
||||||
|
after the given message, but it retrieves the most recent
|
||||||
|
messages. For example, if more than twenty messages are created
|
||||||
|
after the since_id message, using this parameter will omit the
|
||||||
|
messages that immediately follow the given message. This is a
|
||||||
|
bit counterintuitive, so take care.
|
||||||
|
If no messages are found (e.g. when filtering with before_id)
|
||||||
|
we return code 304.
|
||||||
|
Note that for historical reasons, likes are returned as an
|
||||||
|
array of user ids in the favorited_by key.
|
||||||
|
*/
|
||||||
|
func (c *Client) IndexMessages(ctx context.Context, groupID ID, req *IndexMessagesQuery, authToken string) (IndexMessagesResponse, error) {
|
||||||
|
url := fmt.Sprintf(c.endpointBase+indexMessagesEndpoint, groupID)
|
||||||
|
httpReq, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return IndexMessagesResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
URL := httpReq.URL
|
||||||
|
query := URL.Query()
|
||||||
|
if req != nil {
|
||||||
|
if req.BeforeID != "" {
|
||||||
|
query.Add("before_id", req.BeforeID.String())
|
||||||
|
}
|
||||||
|
if req.SinceID != "" {
|
||||||
|
query.Add("since_id", req.SinceID.String())
|
||||||
|
}
|
||||||
|
if req.AfterID != "" {
|
||||||
|
query.Add("after_id", req.AfterID.String())
|
||||||
|
}
|
||||||
|
if req.Limit != 0 {
|
||||||
|
query.Add("limit", strconv.Itoa(req.Limit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var resp IndexMessagesResponse
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return IndexMessagesResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CreateMessage - Send a message to a group
|
||||||
|
|
||||||
|
If you want to attach an image, you must first process it
|
||||||
|
through our image service.
|
||||||
|
|
||||||
|
Attachments of type emoji rely on data from emoji PowerUps.
|
||||||
|
|
||||||
|
Clients use a placeholder character in the message text and
|
||||||
|
specify a replacement charmap to substitute emoji characters
|
||||||
|
|
||||||
|
The character map is an array of arrays containing rune data
|
||||||
|
([[{pack_id,offset}],...]).
|
||||||
|
|
||||||
|
The placeholder should be a high-point/invisible UTF-8 character.
|
||||||
|
*/
|
||||||
|
func (c *Client) CreateMessage(ctx context.Context, groupID ID, m *Message, authToken string) (*Message, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase+createMessagesEndpoint, groupID)
|
||||||
|
|
||||||
|
m.SourceGUID = uuid.New().String()
|
||||||
|
var data = struct {
|
||||||
|
Message *Message `json:"message"`
|
||||||
|
}{
|
||||||
|
m,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
*Message `json:"message"`
|
||||||
|
}
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Message, nil
|
||||||
|
}
|
197
messages_api_test.go
Normal file
197
messages_api_test.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessagesAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *MessagesAPISuite) SetupSuite() {
|
||||||
|
s.handler = messagesTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MessagesAPISuite) TestMessagesIndex() {
|
||||||
|
resp, err := s.client.IndexMessages(
|
||||||
|
context.Background(),
|
||||||
|
ID("123"),
|
||||||
|
&IndexMessagesQuery{
|
||||||
|
BeforeID: "0123456789",
|
||||||
|
SinceID: "9876543210",
|
||||||
|
AfterID: "0246813579",
|
||||||
|
Limit: 20,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotZero(resp)
|
||||||
|
for _, message := range resp.Messages {
|
||||||
|
s.Assert().NotZero(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MessagesAPISuite) TestMessagesCreate() {
|
||||||
|
message, err := s.client.CreateMessage(
|
||||||
|
context.Background(),
|
||||||
|
ID("123"),
|
||||||
|
&Message{
|
||||||
|
Text: "Test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotNil(message)
|
||||||
|
s.Assert().NotZero(*message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessagesAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MessagesAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func messagesTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Index
|
||||||
|
router.Path("/groups/{id:[0-9]+}/messages").
|
||||||
|
Methods("GET").
|
||||||
|
Name("IndexMessages").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"count": 123,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"system": true,
|
||||||
|
"favorited_by": [
|
||||||
|
"101",
|
||||||
|
"66",
|
||||||
|
"1234567890"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create
|
||||||
|
router.Path("/groups/{id:[0-9]+}/messages").
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateMessages").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"message": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"source_guid": "GUID",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"group_id": "1234567890",
|
||||||
|
"name": "John",
|
||||||
|
"avatar_url": "https://i.groupme.com/123456789",
|
||||||
|
"text": "Hello world ☃☃",
|
||||||
|
"system": true,
|
||||||
|
"favorited_by": [
|
||||||
|
"101",
|
||||||
|
"66",
|
||||||
|
"1234567890"
|
||||||
|
],
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://i.groupme.com/123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "location",
|
||||||
|
"lat": "40.738206",
|
||||||
|
"lng": "-73.993285",
|
||||||
|
"name": "GroupMe HQ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "split",
|
||||||
|
"token": "SPLIT_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "emoji",
|
||||||
|
"placeholder": "☃",
|
||||||
|
"charmap": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 201,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
196
real_time.go
Normal file
196
real_time.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"gitea.watsonlabs.net/watsonb8/fayec"
|
||||||
|
"gitea.watsonlabs.net/watsonb8/fayec/message"
|
||||||
|
"gitea.watsonlabs.net/watsonb8/fayec/subscription"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PushServer = "wss://push.groupme.com/faye"
|
||||||
|
userChannel = "/user/"
|
||||||
|
groupChannel = "/group/"
|
||||||
|
dmChannel = "/direct_message/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrHandlerNotFound = errors.New("Handler not found")
|
||||||
|
ErrListenerNotStarted = errors.New("GroupMe listener not started")
|
||||||
|
)
|
||||||
|
|
||||||
|
var concur = sync.Mutex{}
|
||||||
|
|
||||||
|
type HandlerAll interface {
|
||||||
|
Handler
|
||||||
|
|
||||||
|
//of self
|
||||||
|
HandlerText
|
||||||
|
HandlerLike
|
||||||
|
HandlerMembership
|
||||||
|
|
||||||
|
//of group
|
||||||
|
HandleGroupTopic
|
||||||
|
HandleGroupAvatar
|
||||||
|
HandleGroupName
|
||||||
|
HandleGroupLikeIcon
|
||||||
|
|
||||||
|
//of group members
|
||||||
|
HandleMemberNewNickname
|
||||||
|
HandleMemberNewAvatar
|
||||||
|
HandleMembers
|
||||||
|
}
|
||||||
|
type Handler interface {
|
||||||
|
HandleError(error)
|
||||||
|
}
|
||||||
|
type HandlerText interface {
|
||||||
|
HandleTextMessage(Message)
|
||||||
|
}
|
||||||
|
type HandlerLike interface {
|
||||||
|
HandleLike(Message)
|
||||||
|
}
|
||||||
|
type HandlerMembership interface {
|
||||||
|
HandleJoin(ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Handlers
|
||||||
|
type HandleGroupTopic interface {
|
||||||
|
HandleGroupTopic(group ID, newTopic string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandleGroupName interface {
|
||||||
|
HandleGroupName(group ID, newName string)
|
||||||
|
}
|
||||||
|
type HandleGroupAvatar interface {
|
||||||
|
HandleGroupAvatar(group ID, newAvatar string)
|
||||||
|
}
|
||||||
|
type HandleGroupLikeIcon interface {
|
||||||
|
HandleLikeIcon(group ID, PackID, PackIndex int, Type string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group member handlers
|
||||||
|
type HandleMemberNewNickname interface {
|
||||||
|
HandleNewNickname(group ID, user ID, newName string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandleMemberNewAvatar interface {
|
||||||
|
HandleNewAvatarInGroup(group ID, user ID, avatarURL string)
|
||||||
|
}
|
||||||
|
type HandleMembers interface {
|
||||||
|
//HandleNewMembers returns only partial member with id and nickname; added is false if removing
|
||||||
|
HandleMembers(group ID, members []Member, added bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushSubscription manages real time subscription
|
||||||
|
type PushSubscription struct {
|
||||||
|
channel chan message.Data
|
||||||
|
client *fayec.Client
|
||||||
|
handlers map[string][]Handler // key == token
|
||||||
|
LastConnected int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPushSubscription creates and returns a push subscription object
|
||||||
|
func NewPushSubscription(context context.Context) PushSubscription {
|
||||||
|
|
||||||
|
r := PushSubscription{
|
||||||
|
channel: make(chan message.Data),
|
||||||
|
handlers: make(map[string][]Handler),
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PushSubscription) AddHandler(h Handler, authToken string) {
|
||||||
|
if r.handlers[authToken] == nil {
|
||||||
|
r.handlers[authToken] = []Handler{h}
|
||||||
|
} else {
|
||||||
|
r.handlers[authToken] = append(r.handlers[authToken], h)
|
||||||
|
}
|
||||||
|
//r.handlers = append(r.handlers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFullHandler is the same as AddHandler except it ensures the interface implements everything
|
||||||
|
func (r *PushSubscription) AddFullHandler(h HandlerAll, authToken string) {
|
||||||
|
if r.handlers[authToken] == nil {
|
||||||
|
r.handlers[authToken] = []Handler{h}
|
||||||
|
} else {
|
||||||
|
r.handlers[authToken] = append(r.handlers[authToken], h)
|
||||||
|
}
|
||||||
|
|
||||||
|
//r.handlers = append(r.handlers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
var RealTimeHandlers map[string]func(r *PushSubscription, channel string, authToken string, data ...interface{})
|
||||||
|
var RealTimeSystemHandlers map[string]func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte)
|
||||||
|
|
||||||
|
// Listen connects to GroupMe. Runs in Goroutine.
|
||||||
|
func (r *PushSubscription) Connect(context context.Context) error {
|
||||||
|
c, err := fayec.NewClient(PushServer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.client = c
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeToUser to users
|
||||||
|
func (r *PushSubscription) SubscribeToUser(context context.Context, id ID, authToken string) error {
|
||||||
|
return r.subscribeWithPrefix(userChannel, context, id, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeToGroup to groups for typing notification
|
||||||
|
func (r *PushSubscription) SubscribeToGroup(context context.Context, id ID, authToken string) error {
|
||||||
|
return r.subscribeWithPrefix(groupChannel, context, id, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeToDM to users
|
||||||
|
func (r *PushSubscription) SubscribeToDM(context context.Context, id ID, authToken string) error {
|
||||||
|
id = ID(strings.Replace(id.String(), "+", "_", 1))
|
||||||
|
return r.subscribeWithPrefix(dmChannel, context, id, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PushSubscription) subscribeWithPrefix(prefix string, context context.Context, groupID ID, authToken string) error {
|
||||||
|
concur.Lock()
|
||||||
|
defer concur.Unlock()
|
||||||
|
if r.client == nil {
|
||||||
|
return ErrListenerNotStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
var sub *subscription.Subscription
|
||||||
|
sub, err := r.client.Subscribe(prefix+groupID.String(), authToken)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sub.OnMessage(func(channel string, data message.Data) {
|
||||||
|
r.LastConnected = time.Now().Unix()
|
||||||
|
dataMap := data.(map[string]interface{})
|
||||||
|
content := dataMap["subject"]
|
||||||
|
contentType := dataMap["type"].(string)
|
||||||
|
|
||||||
|
handler, ok := RealTimeHandlers[contentType]
|
||||||
|
if !ok {
|
||||||
|
if contentType == "ping" ||
|
||||||
|
len(contentType) == 0 ||
|
||||||
|
content == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("Unable to handle GroupMe message type", contentType)
|
||||||
|
} else {
|
||||||
|
handler(r, channel, authToken, content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connected check if connected
|
||||||
|
func (r *PushSubscription) Connected() bool {
|
||||||
|
return r.LastConnected+30 >= time.Now().Unix()
|
||||||
|
}
|
266
real_time_handler.go
Normal file
266
real_time_handler.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
RealTimeHandlers = make(map[string]func(r *PushSubscription, channel string, authToken string, data ...interface{}))
|
||||||
|
|
||||||
|
//Base Handlers on user channel
|
||||||
|
RealTimeHandlers["direct_message.create"] = func(r *PushSubscription, channel string, authToken string, data ...interface{}) {
|
||||||
|
b, _ := json.Marshal(data[0])
|
||||||
|
out := Message{}
|
||||||
|
_ = json.Unmarshal(b, &out)
|
||||||
|
|
||||||
|
//maybe something with API versioning
|
||||||
|
out.ConversationID = out.ChatID
|
||||||
|
|
||||||
|
if out.UserID.String() == "system" {
|
||||||
|
event := struct {
|
||||||
|
Event struct {
|
||||||
|
Kind string `json:"type"`
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &event)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
rawData, _ := json.Marshal(event.Event.Data)
|
||||||
|
handler, ok := RealTimeSystemHandlers[event.Event.Kind]
|
||||||
|
if !ok {
|
||||||
|
log.Println("Unable to handle system message of type", event.Event.Kind)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := out.GroupID
|
||||||
|
if len(id) == 0 {
|
||||||
|
id = out.ConversationID
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(r, channel, id, authToken, rawData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandlerText); ok {
|
||||||
|
h.HandleTextMessage(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeHandlers["line.create"] = RealTimeHandlers["direct_message.create"]
|
||||||
|
|
||||||
|
RealTimeHandlers["like.create"] = func(r *PushSubscription, channel string, authToken string, data ...interface{}) { //should be an associated chatEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeHandlers["membership.create"] = func(r *PushSubscription, channel string, authToken string, data ...interface{}) {
|
||||||
|
c, _ := data[0].(map[string]interface{})
|
||||||
|
id, _ := c["id"].(string)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandlerMembership); ok {
|
||||||
|
h.HandleJoin(ID(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//following are for each chat
|
||||||
|
RealTimeHandlers["favorite"] = func(r *PushSubscription, channel string, authToken string, data ...interface{}) {
|
||||||
|
c, ok := data[0].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
fmt.Println(data, "err")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e, ok := c["line"]
|
||||||
|
if !ok {
|
||||||
|
fmt.Println(data, "err")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d, _ := json.Marshal(e)
|
||||||
|
msg := Message{}
|
||||||
|
_ = json.Unmarshal(d, &msg)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandlerLike); ok {
|
||||||
|
h.HandleLike(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//following are for messages from system (administrative/settings changes)
|
||||||
|
RealTimeSystemHandlers = make(map[string]func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte))
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["membership.nickname_changed"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
thing := struct {
|
||||||
|
Name string
|
||||||
|
User struct {
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &thing)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleMemberNewNickname); ok {
|
||||||
|
h.HandleNewNickname(id, ID(strconv.Itoa(thing.User.ID)), thing.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["membership.avatar_changed"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
content := struct {
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
User struct {
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &content)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleMemberNewAvatar); ok {
|
||||||
|
h.HandleNewAvatarInGroup(id, ID(strconv.Itoa(content.User.ID)), content.AvatarURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["membership.announce.added"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
data := struct {
|
||||||
|
Added []Member `json:"added_users"`
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleMembers); ok {
|
||||||
|
h.HandleMembers(id, data.Added, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["membership.notifications.removed"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
data := struct {
|
||||||
|
Added Member `json:"removed_user"`
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleMembers); ok {
|
||||||
|
h.HandleMembers(id, []Member{data.Added}, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["membership.name_change"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Name string
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleGroupName); ok {
|
||||||
|
h.HandleGroupName(id, data.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["group.name_change"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Name string
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleGroupName); ok {
|
||||||
|
h.HandleGroupName(id, data.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["group.topic_change"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Topic string
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleGroupTopic); ok {
|
||||||
|
h.HandleGroupTopic(id, data.Topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["group.avatar_change"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
data := struct {
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleGroupAvatar); ok {
|
||||||
|
h.HandleGroupAvatar(id, data.AvatarURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["group.like_icon_set"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
data := struct {
|
||||||
|
LikeIcon struct {
|
||||||
|
PackID int `json:"pack_id"`
|
||||||
|
PackIndex int `json:"pack_index"`
|
||||||
|
Type string
|
||||||
|
} `json:"like_icon"`
|
||||||
|
}{}
|
||||||
|
_ = json.Unmarshal(rawData, &data)
|
||||||
|
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleGroupLikeIcon); ok {
|
||||||
|
h.HandleLikeIcon(id, data.LikeIcon.PackID, data.LikeIcon.PackIndex, data.LikeIcon.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealTimeSystemHandlers["group.like_icon_removed"] = func(r *PushSubscription, channel string, id ID, authToken string, rawData []byte) {
|
||||||
|
handlers := r.handlers[authToken]
|
||||||
|
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h, ok := h.(HandleGroupLikeIcon); ok {
|
||||||
|
h.HandleLikeIcon(id, 0, 0, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
86
sms_mode_api.go
Normal file
86
sms_mode_api.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#sms_mode
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
smsModeEndpointRoot = usersEndpointRoot + "/sms_mode"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
createSMSModeEndpoint = smsModeEndpointRoot // POST
|
||||||
|
deleteSMSModeEndpoint = smsModeEndpointRoot + "/delete" // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// Create
|
||||||
|
|
||||||
|
/*
|
||||||
|
CreateSMSMode -
|
||||||
|
Enables SMS mode for N hours, where N is at most 48. After N
|
||||||
|
hours have elapsed, user will receive push notfications.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
duration - required, integer
|
||||||
|
registration_id - string; The push notification ID/token
|
||||||
|
that should be suppressed during SMS mode. If this is
|
||||||
|
omitted, both SMS and push notifications will be
|
||||||
|
delivered to the device.
|
||||||
|
*/
|
||||||
|
func (c *Client) CreateSMSMode(ctx context.Context, duration int, registrationID *ID, authToken string) error {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + createSMSModeEndpoint)
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
RegistrationID *ID `json:"registration_id,omitempty"`
|
||||||
|
}{
|
||||||
|
duration,
|
||||||
|
registrationID,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
|
||||||
|
/*
|
||||||
|
DeleteSMSMode -
|
||||||
|
|
||||||
|
Disables SMS mode
|
||||||
|
*/
|
||||||
|
func (c *Client) DeleteSMSMode(ctx context.Context, authToken string) error {
|
||||||
|
url := fmt.Sprintf(c.endpointBase + deleteSMSModeEndpoint)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuthToken(ctx, httpReq, nil, authToken)
|
||||||
|
}
|
53
sms_mode_api_test.go
Normal file
53
sms_mode_api_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SMSModeAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *SMSModeAPISuite) SetupSuite() {
|
||||||
|
s.handler = smsModeTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMSModeAPISuite) TestSMSModeCreate() {
|
||||||
|
s.Assert().NoError(s.client.CreateSMSMode(context.Background(), 10, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMSModeAPISuite) TestSMSModeDelete() {
|
||||||
|
s.Assert().NoError(s.client.DeleteSMSMode(context.Background()))
|
||||||
|
}
|
||||||
|
func TestSMSModeAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(SMSModeAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func smsModeTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Create
|
||||||
|
router.Path("/users/sms_mode").
|
||||||
|
Methods("POST").
|
||||||
|
Name("CreateSMSMode").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(201)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
router.Path("/users/sms_mode/delete").
|
||||||
|
Methods("POST").
|
||||||
|
Name("DeleteSMSMode").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
93
users_api.go
Normal file
93
users_api.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupMe documentation: https://dev.groupme.com/docs/v3#users
|
||||||
|
|
||||||
|
/*//////// Endpoints ////////*/
|
||||||
|
const (
|
||||||
|
// Used to build other endpoints
|
||||||
|
usersEndpointRoot = "/users"
|
||||||
|
|
||||||
|
// Actual Endpoints
|
||||||
|
myUserEndpoint = usersEndpointRoot + "/me" // GET
|
||||||
|
updateMyUserEndpoint = usersEndpointRoot + "/update" // POST
|
||||||
|
)
|
||||||
|
|
||||||
|
/*//////// API Requests ////////*/
|
||||||
|
|
||||||
|
// Me
|
||||||
|
|
||||||
|
/*
|
||||||
|
MyUser -
|
||||||
|
|
||||||
|
Loads a specific group.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
groupID - required, ID(string)
|
||||||
|
*/
|
||||||
|
func (c *Client) MyUser(ctx context.Context, authToken string) (*User, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + myUserEndpoint)
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("GET", URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp User
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserSettings are the settings for a GroupMe user
|
||||||
|
type UserSettings struct {
|
||||||
|
// URL to valid JPG/PNG/GIF image. URL will be converted into
|
||||||
|
// an image service link (https://i.groupme.com/....)
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
// Name must be of the form FirstName LastName
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Email address. Must be in name@domain.com form
|
||||||
|
Email string `json:"email"`
|
||||||
|
ZipCode string `json:"zip_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateMyUser -
|
||||||
|
|
||||||
|
# Update attributes about your own account
|
||||||
|
|
||||||
|
Parameters: See UserSettings
|
||||||
|
*/
|
||||||
|
func (c *Client) UpdateMyUser(ctx context.Context, us UserSettings, authToken string) (*User, error) {
|
||||||
|
URL := fmt.Sprintf(c.endpointBase + updateMyUserEndpoint)
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(&us)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp User
|
||||||
|
err = c.doWithAuthToken(ctx, httpReq, &resp, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
91
users_api_test.go
Normal file
91
users_api_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Package groupme defines a client capable of executing API commands for the GroupMe chat service
|
||||||
|
package groupme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UsersAPISuite struct{ APISuite }
|
||||||
|
|
||||||
|
func (s *UsersAPISuite) SetupSuite() {
|
||||||
|
s.handler = usersTestRouter()
|
||||||
|
s.setupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsersAPISuite) TestUsersMe() {
|
||||||
|
user, err := s.client.MyUser(context.Background())
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsersAPISuite) TestUsersUpdate() {
|
||||||
|
user, err := s.client.UpdateMyUser(context.Background(), UserSettings{})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().NotZero(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(UsersAPISuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint // not duplicate code
|
||||||
|
func usersTestRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter().Queries("token", "").Subrouter()
|
||||||
|
|
||||||
|
// Me
|
||||||
|
router.Path("/users/me").
|
||||||
|
Methods("GET").
|
||||||
|
Name("MyUser").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"phone_number": "+1 2123001234",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"name": "Ronald Swanson",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"email": "me@example.com",
|
||||||
|
"sms": false
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update
|
||||||
|
router.Path("/users/update").
|
||||||
|
Methods("POST").
|
||||||
|
Name("UpdateMyUser").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"response": {
|
||||||
|
"id": "1234567890",
|
||||||
|
"phone_number": "+1 2123001234",
|
||||||
|
"image_url": "https://i.groupme.com/123456789",
|
||||||
|
"name": "Ronald Swanson",
|
||||||
|
"created_at": 1302623328,
|
||||||
|
"updated_at": 1302623328,
|
||||||
|
"email": "me@example.com",
|
||||||
|
"sms": false
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"code": 200,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*// Return test router //*/
|
||||||
|
return router
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user