Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
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) ([]*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)
|
||||||
|
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) (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)
|
||||||
|
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) (*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)
|
||||||
|
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) 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)
|
||||||
|
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) (*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)
|
||||||
|
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) ([]*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)
|
||||||
|
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) 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)
|
||||||
|
}
|
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
|
||||||
|
}
|
55
chats_api.go
Normal file
55
chats_api.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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) ([]*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)
|
||||||
|
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
|
||||||
|
}
|
122
client.go
Normal file
122
client.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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
|
||||||
|
authorizationToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new GroupMe API Client
|
||||||
|
func NewClient(authToken string) *Client {
|
||||||
|
return &Client{
|
||||||
|
// TODO: enable transport information passing in
|
||||||
|
httpClient: &http.Client{},
|
||||||
|
endpointBase: GroupMeAPIBase,
|
||||||
|
authorizationToken: authToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{}) error {
|
||||||
|
URL := req.URL
|
||||||
|
query := URL.Query()
|
||||||
|
query.Set("token", c.authorizationToken)
|
||||||
|
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))
|
||||||
|
}
|
139
direct_messages_api.go
Normal file
139
direct_messages_api.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// 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) (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)
|
||||||
|
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) (*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)
|
||||||
|
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
|
||||||
|
}
|
52
examples/group_messages/main.go
Normal file
52
examples/group_messages/main.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(
|
||||||
|
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)
|
||||||
|
}
|
21
examples/post_bot_message/main.go
Normal file
21
examples/post_bot_message/main.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/densestvoid/groupme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
9
go.mod
Executable file
9
go.mod
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
module github.com/densestvoid/groupme
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.2.0
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
)
|
16
go.sum
Executable file
16
go.sum
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
|
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=
|
||||||
|
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=
|
414
groups_api.go
Executable file
414
groups_api.go
Executable file
@ -0,0 +1,414 @@
|
|||||||
|
// 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) ([]*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)
|
||||||
|
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) ([]*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)
|
||||||
|
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) (*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)
|
||||||
|
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) (*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)
|
||||||
|
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) (*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)
|
||||||
|
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) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// 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) (*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)
|
||||||
|
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) (*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)
|
||||||
|
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) (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)
|
||||||
|
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
|
||||||
|
}
|
231
json.go
Executable file
231
json.go
Executable file
@ -0,0 +1,231 @@
|
|||||||
|
// 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"`
|
||||||
|
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))
|
||||||
|
}
|
119
leaderboard_api.go
Normal file
119
leaderboard_api.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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) ([]*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)
|
||||||
|
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) ([]*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)
|
||||||
|
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) ([]*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)
|
||||||
|
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) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyLike - Unlike a message.
|
||||||
|
func (c *Client) DestroyLike(ctx context.Context, conversationID, messageID ID) 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)
|
||||||
|
}
|
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())
|
||||||
|
}
|
182
members_api.go
Normal file
182
members_api.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
// 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, 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)
|
||||||
|
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) ([]*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)
|
||||||
|
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) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// 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) (*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)
|
||||||
|
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) (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)
|
||||||
|
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) (*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)
|
||||||
|
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
|
||||||
|
}
|
85
sms_mode_api.go
Normal file
85
sms_mode_api.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// 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) 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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
|
||||||
|
/*
|
||||||
|
DeleteSMSMode -
|
||||||
|
|
||||||
|
Disables SMS mode
|
||||||
|
*/
|
||||||
|
func (c *Client) DeleteSMSMode(ctx context.Context) 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)
|
||||||
|
}
|
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
|
||||||
|
}
|
92
users_api.go
Normal file
92
users_api.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// 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) (*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)
|
||||||
|
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) (*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)
|
||||||
|
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
|
||||||
|
}
|
Reference in New Issue
Block a user