func (cs *coreService) EditMessage(ctx context.Context, roomID string, messageID uint, options EditMessageOptions) error {
	if options.Text == "" {
		return errors.New("You must provide some text for the message")
	}
	if options.SenderID == "" {
		return errors.New("You must provide the ID of the user editing the message")
	}
	if roomID == "" {
		return errors.New("You must provide the ID of the room in which the message to edit belongs")
	}
	requestBody, err := common.CreateRequestBody(map[string]string{"text": options.Text})
	if err != nil {
		return err
	}
	response, err := common.RequestWithUserToken(
		cs.underlyingInstance,
		ctx,
		options.SenderID,
		client.RequestOptions{
			Method: http.MethodPut,
			Path:   fmt.Sprintf("/rooms/%s/messages/%d", url.PathEscape(roomID), messageID),
			Body:   requestBody,
		})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
EditMessage edits an existing message in a room.
package core
import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"golang.org/x/sync/errgroup"
	"github.com/pusher/pusher-platform-go/client"
	"github.com/pusher/pusher-platform-go/instance"
	"github.com/pusher/chatkit-server-go/internal/common"
)
type Service interface {
	GetUser(ctx context.Context, userID string) (User, error)
	GetUsers(ctx context.Context, options *GetUsersOptions) ([]User, error)
	GetUsersByID(ctx context.Context, userIDs []string) ([]User, error)
	CreateUser(ctx context.Context, options CreateUserOptions) error
	CreateUsers(ctx context.Context, users []CreateUserOptions) error
	UpdateUser(ctx context.Context, userID string, options UpdateUserOptions) error
	DeleteUser(ctx context.Context, userID string) error
	GetRoom(ctx context.Context, roomID string) (Room, error)
	GetRooms(ctx context.Context, options GetRoomsOptions) ([]RoomWithoutMembers, error)
	GetUserRooms(ctx context.Context, userID string) ([]Room, error)
	GetUserJoinableRooms(ctx context.Context, userID string) ([]Room, error)
	CreateRoom(ctx context.Context, options CreateRoomOptions) (Room, error)
	UpdateRoom(ctx context.Context, roomID string, options UpdateRoomOptions) error
	DeleteRoom(ctx context.Context, roomID string) error
	AddUsersToRoom(ctx context.Context, roomID string, userIDs []string) error
	RemoveUsersFromRoom(ctx context.Context, roomID string, userIds []string) error
	SendMessage(ctx context.Context, options SendMessageOptions) (uint, error)
	SendMultipartMessage(ctx context.Context, options SendMultipartMessageOptions) (uint, error)
	SendSimpleMessage(ctx context.Context, options SendSimpleMessageOptions) (uint, error)
	GetRoomMessages(
		ctx context.Context,
		roomID string,
		options GetRoomMessagesOptions,
	) ([]Message, error)
	FetchMultipartMessage(
		ctx context.Context,
		options FetchMultipartMessageOptions,
	) (MultipartMessage, error)
	FetchMultipartMessages(
		ctx context.Context,
		roomID string,
		options FetchMultipartMessagesOptions,
	) ([]MultipartMessage, error)
	DeleteMessage(ctx context.Context, options DeleteMessageOptions) error
	EditMessage(ctx context.Context, roomID string, messageID uint, options EditMessageOptions) error
	EditMultipartMessage(ctx context.Context, roomID string, messageID uint, options EditMultipartMessageOptions) error
	EditSimpleMessage(ctx context.Context, roomID string, messageID uint, options EditSimpleMessageOptions) error
	Request(ctx context.Context, options client.RequestOptions) (*http.Response, error)
}
type coreService struct {
	underlyingInstance instance.Instance
}
func NewService(platformInstance instance.Instance) Service {
	return &coreService{
		underlyingInstance: platformInstance,
	}
}
func (cs *coreService) GetUser(ctx context.Context, userID string) (User, error) {
	if userID == "" {
		return User{}, errors.New("You must provide the ID of the user you want to fetch")
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodGet,
		Path:   fmt.Sprintf("/users/%s", url.PathEscape(userID)),
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return User{}, err
	}
	var user User
	err = common.DecodeResponseBody(response.Body, &user)
	if err != nil {
		return User{}, err
	}
	return user, nil
}
func (cs *coreService) GetUsers(ctx context.Context, options *GetUsersOptions) ([]User, error) {
	queryParams := url.Values{}
	if options != nil {
		queryParams.Add("from_ts", options.FromTimestamp)
		queryParams.Add("limit", strconv.Itoa(int(options.Limit)))
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method:      http.MethodGet,
		Path:        "/users",
		QueryParams: &queryParams,
	})
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	var users []User
	err = common.DecodeResponseBody(response.Body, &users)
	if err != nil {
		return nil, err
	}
	return users, nil
}
func (cs *coreService) GetUsersByID(ctx context.Context, userIDs []string) ([]User, error) {
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodGet,
		Path:   "/users_by_ids",
		QueryParams: &url.Values{
			"id": userIDs,
		},
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return nil, err
	}
	var users []User
	err = common.DecodeResponseBody(response.Body, &users)
	if err != nil {
		return nil, err
	}
	return users, nil
}
func (cs *coreService) CreateUser(ctx context.Context, options CreateUserOptions) error {
	if options.ID == "" {
		return errors.New("You must provide the ID of the user to create")
	}
	if options.Name == "" {
		return errors.New("You must provide the name of the user to create")
	}
	requestBody, err := common.CreateRequestBody(&options)
	if err != nil {
		return err
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodPost,
		Path:   "/users",
		Body:   requestBody,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) CreateUsers(ctx context.Context, users []CreateUserOptions) error {
	if len(users) == 0 {
		return errors.New("You must provide a list of users to create")
	}
	requestBody, err := common.CreateRequestBody(map[string]interface{}{"users": users})
	if err != nil {
		return err
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodPost,
		Path:   "/batch_users",
		Body:   requestBody,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) UpdateUser(
	ctx context.Context,
	userID string,
	options UpdateUserOptions,
) error {
	if userID == "" {
		return errors.New("You must provide the ID of the user to update")
	}
	requestBody, err := common.CreateRequestBody(&options)
	if err != nil {
		return err
	}
	response, err := common.RequestWithUserToken(cs.underlyingInstance, ctx, userID, client.RequestOptions{
		Method: http.MethodPut,
		Path:   fmt.Sprintf("/users/%s", url.PathEscape(userID)),
		Body:   requestBody,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) DeleteUser(ctx context.Context, userID string) error {
	if userID == "" {
		return errors.New("You must provide the ID of the user to delete")
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodDelete,
		Path:   fmt.Sprintf("/users/%s", url.PathEscape(userID)),
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) GetRoom(ctx context.Context, roomID string) (Room, error) {
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodGet,
		Path:   fmt.Sprintf("/rooms/%s", url.PathEscape(roomID)),
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return Room{}, err
	}
	var room Room
	err = common.DecodeResponseBody(response.Body, &room)
	if err != nil {
		return Room{}, err
	}
	return room, nil
}
func (cs *coreService) GetRooms(ctx context.Context, options GetRoomsOptions) ([]RoomWithoutMembers, error) {
	queryParams := url.Values{}
	if options.FromID != nil {
		queryParams.Add("from_id", *options.FromID)
	}
	strIncludePrivate := "false"
	if options.IncludePrivate {
		strIncludePrivate = "true"
	}
	queryParams.Add("include_private", strIncludePrivate)
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method:      http.MethodGet,
		Path:        "/rooms",
		QueryParams: &queryParams,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return nil, err
	}
	var rooms []RoomWithoutMembers
	err = common.DecodeResponseBody(response.Body, &rooms)
	if err != nil {
		return nil, err
	}
	return rooms, nil
}
func (cs *coreService) GetUserRooms(ctx context.Context, userID string) ([]Room, error) {
	return cs.getRoomsForUser(ctx, userID, false)
}
func (cs *coreService) GetUserJoinableRooms(ctx context.Context, userID string) ([]Room, error) {
	return cs.getRoomsForUser(ctx, userID, true)
}
func (cs *coreService) getRoomsForUser(
	ctx context.Context,
	userID string,
	joinable bool,
) ([]Room, error) {
	if userID == "" {
		return nil, errors.New("You must privde the ID of the user to retrieve rooms for")
	}
	strJoinable := "false"
	if joinable {
		strJoinable = "true"
	}
	queryParams := url.Values{"joinable": []string{strJoinable}}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method:      http.MethodGet,
		Path:        fmt.Sprintf("/users/%s/rooms", url.PathEscape(userID)),
		QueryParams: &queryParams,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return nil, err
	}
	var rooms []Room
	err = common.DecodeResponseBody(response.Body, &rooms)
	if err != nil {
		return nil, err
	}
	return rooms, nil
}
func (cs *coreService) CreateRoom(ctx context.Context, options CreateRoomOptions) (Room, error) {
	if options.CreatorID == "" {
		return Room{}, errors.New("Yout must provide the ID of the user creating the room")
	}
	if options.Name == "" {
		return Room{}, errors.New("You must provide a name for the room")
	}
	requestBody, err := common.CreateRequestBody(&options)
	if err != nil {
		return Room{}, err
	}
	response, err := common.RequestWithUserToken(
		cs.underlyingInstance,
		ctx,
		options.CreatorID, client.RequestOptions{
			Method: http.MethodPost,
			Path:   "/rooms",
			Body:   requestBody,
		},
	)
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return Room{}, err
	}
	var room Room
	err = common.DecodeResponseBody(response.Body, &room)
	if err != nil {
		return Room{}, err
	}
	return room, nil
}
func (cs *coreService) UpdateRoom(ctx context.Context, roomID string, options UpdateRoomOptions) error {
	var formattedOptions interface{} = options
	if options.PushNotificationTitleOverride == &ExplicitlyResetPushNotificationTitleOverride {
		type updateRoomOptionsWithExplicitPNTitleOverride struct {
			UpdateRoomOptions
			PushNotificationTitleOverride *string `json:"push_notification_title_override"`
		}
		formattedOptions = updateRoomOptionsWithExplicitPNTitleOverride{
			UpdateRoomOptions:             options,
			PushNotificationTitleOverride: nil,
		}
	}
	requestBody, err := common.CreateRequestBody(&formattedOptions)
	if err != nil {
		return err
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodPut,
		Path:   fmt.Sprintf("/rooms/%s", url.PathEscape(roomID)),
		Body:   requestBody,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) DeleteRoom(ctx context.Context, roomID string) error {
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodDelete,
		Path:   fmt.Sprintf("/rooms/%s", url.PathEscape(roomID)),
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) AddUsersToRoom(ctx context.Context, roomID string, userIDs []string) error {
	if len(userIDs) == 0 {
		return errors.New("You must provide a list of IDs of the users you want to add to the room")
	}
	requestBody, err := common.CreateRequestBody(map[string][]string{"user_ids": userIDs})
	if err != nil {
		return err
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodPut,
		Path:   fmt.Sprintf("/rooms/%s/users/add", url.PathEscape(roomID)),
		Body:   requestBody,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) RemoveUsersFromRoom(ctx context.Context, roomID string, userIDs []string) error {
	if len(userIDs) == 0 {
		return errors.New("You must provide a list of IDs of the users you want to remove from the room")
	}
	requestBody, err := common.CreateRequestBody(map[string][]string{"user_ids": userIDs})
	if err != nil {
		return err
	}
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodPut,
		Path:   fmt.Sprintf("/rooms/%s/users/remove", url.PathEscape(roomID)),
		Body:   requestBody,
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func (cs *coreService) SendMessage(ctx context.Context, options SendMessageOptions) (uint, error) {
	if options.Text == "" {
		return 0, errors.New("You must provide some text for the message")
	}
	if options.SenderID == "" {
		return 0, errors.New("You must provide the ID of the user sending the message")
	}
	requestBody, err := common.CreateRequestBody(map[string]string{"text": options.Text})
	if err != nil {
		return 0, err
	}
	response, err := common.RequestWithUserToken(
		cs.underlyingInstance,
		ctx,
		options.SenderID,
		client.RequestOptions{
			Method: http.MethodPost,
			Path:   fmt.Sprintf("/rooms/%s/messages", url.PathEscape(options.RoomID)),
			Body:   requestBody,
		})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return 0, err
	}
	var messageResponse map[string]uint
	err = common.DecodeResponseBody(response.Body, &messageResponse)
	if err != nil {
		return 0, err
	}
	return messageResponse["message_id"], nil
}
func (cs *coreService) SendMultipartMessage(
	ctx context.Context,
	options SendMultipartMessageOptions,
) (uint, error) {
	if len(options.Parts) == 0 {
		return 0, errors.New("You must provide at least one message part")
	}
	if options.SenderID == "" {
		return 0, errors.New("You must provide the ID of the user sending the message")
	}
	requestParts := make([]interface{}, len(options.Parts))
	g, gCtx := errgroup.WithContext(ctx)
	for i, part := range options.Parts {
		switch p := part.(type) {
		case NewAttachmentPart:
			i := i
			g.Go(func() error {
				uploadedPart, err := cs.uploadAttachment(gCtx, options.SenderID, options.RoomID, p)
				requestParts[i] = uploadedPart
				return err
			})
		default:
			requestParts[i] = part
		}
	}
	if err := g.Wait(); err != nil {
		return 0, fmt.Errorf("Failed to upload attachment: %v", err)
	}
	requestBody, err := common.CreateRequestBody(
		map[string]interface{}{"parts": requestParts},
	)
	if err != nil {
		return 0, err
	}
	response, err := common.RequestWithUserToken(
		cs.underlyingInstance,
		ctx,
		options.SenderID,
		client.RequestOptions{
			Method: http.MethodPost,
			Path:   fmt.Sprintf("/rooms/%s/messages", url.PathEscape(options.RoomID)),
			Body:   requestBody,
		},
	)
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return 0, err
	}
	var messageResponse map[string]uint
	err = common.DecodeResponseBody(response.Body, &messageResponse)
	if err != nil {
		return 0, err
	}
	return messageResponse["message_id"], nil
}
func (cs *coreService) uploadAttachment(
	ctx context.Context,
	senderID string,
	roomID string,
	part NewAttachmentPart,
) (newAttachmentPartUploaded, error) {
	b, err := ioutil.ReadAll(part.File)
	if err != nil {
		return newAttachmentPartUploaded{}, err
	}
	url, attachmentID, err := cs.requestPresignedURL(
		ctx,
		senderID,
		roomID,
		part.Type,
		len(b),
		part.Name,
		part.CustomData,
	)
	if err != nil {
		return newAttachmentPartUploaded{}, err
	}
	if err := cs.uploadToURL(ctx, url, part.Type, len(b), bytes.NewReader(b)); err != nil {
		return newAttachmentPartUploaded{}, err
	}
	return newAttachmentPartUploaded{
		Type:       part.Type,
		Attachment: uploadedAttachment{attachmentID},
	}, nil
}
func (cs *coreService) requestPresignedURL(
	ctx context.Context,
	senderID string,
	roomID string,
	contentType string,
	contentLength int,
	name *string,
	customData interface{},
) (string, string, error) {
	body, err := common.CreateRequestBody(map[string]interface{}{
		"content_type":   contentType,
		"content_length": contentLength,
		"name":           name,
		"custom_data":    customData,
	})
	if err != nil {
		return "", "", err
	}
	res, err := common.RequestWithUserToken(
		cs.underlyingInstance,
		ctx,
		senderID,
		client.RequestOptions{
			Method: http.MethodPost,
			Path:   fmt.Sprintf("/rooms/%s/attachments", url.PathEscape(roomID)),
			Body:   body,
		},
	)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		return "", "", err
	}
	var resBody map[string]string
	if err := common.DecodeResponseBody(res.Body, &resBody); err != nil {
		return "", "", err
	}
	return resBody["upload_url"], resBody["attachment_id"], nil
}
func (cs *coreService) uploadToURL(
	ctx context.Context,
	url string,
	contentType string,
	contentLength int,
	body io.Reader,
) error {
	client := &http.Client{}
	req, err := http.NewRequest("PUT", url, body)
	if err != nil {
		return err
	}
	req.Header.Add("content-type", contentType)
	req.Header.Add("content-length", strconv.Itoa(contentLength))
	res, err := client.Do(req.WithContext(ctx))
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		return err
	}
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		return fmt.Errorf("unexpected status: %v", res.Status)
	}
	return nil
}
func (cs *coreService) SendSimpleMessage(
	ctx context.Context,
	options SendSimpleMessageOptions,
) (uint, error) {
	return cs.SendMultipartMessage(ctx, SendMultipartMessageOptions{
		RoomID:   options.RoomID,
		SenderID: options.SenderID,
		Parts:    []NewPart{NewInlinePart{Type: "text/plain", Content: options.Text}},
	})
}
func (cs *coreService) DeleteMessage(ctx context.Context, options DeleteMessageOptions) error {
	response, err := common.RequestWithSuToken(cs.underlyingInstance, ctx, client.RequestOptions{
		Method: http.MethodDelete,
		Path:   fmt.Sprintf("/rooms/%s/messages/%d", url.PathEscape(options.RoomID), options.MessageID),
	})
	if response != nil {
		defer response.Body.Close()
	}
	if err != nil {
		return nil
	}
	return nil
}