diff --git a/AUTHORS.txt b/AUTHORS.txt index 389fcd6..5422ec3 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -5,3 +5,4 @@ Maksym Pavlenko Marcus Westin Minko Gechev Scott Gose +Tomasz Tomalak diff --git a/BUILD.bazel b/BUILD.bazel index b2abe6e..cd9f664 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -40,9 +40,11 @@ go_library( "prices_voice.go", "queue.go", "recording.go", + "room.go", "transcription.go", "types.go", "validation.go", + "video_recording.go", "wireless.go", "wireless_commands.go", ], @@ -80,9 +82,11 @@ go_test( "prices_voice_test.go", "recording_test.go", "responses_test.go", + "room_test.go", "transcription_test.go", "types_test.go", "validation_test.go", + "video_recording_test.go", "wireless_commands_test.go", "wireless_test.go", ], diff --git a/http.go b/http.go index af77280..c7b1867 100644 --- a/http.go +++ b/http.go @@ -54,6 +54,11 @@ const NotifyVersion = "v1" const LookupBaseURL = "https://lookups.twilio.com" const LookupVersion = "v1" +// Video service +var VideoBaseUrl = "https://video.twilio.com" + +const VideoVersion = "v1" + type Client struct { *rest.Client Monitor *Client @@ -62,6 +67,7 @@ type Client struct { Wireless *Client Notify *Client Lookup *Client + Video *Client // FullPath takes a path part (e.g. "Messages") and // returns the full API path, including the version (e.g. @@ -108,6 +114,10 @@ type Client struct { // NewLookupClient initializes these services LookupPhoneNumbers *LookupPhoneNumbersService + + // NewVideoClient initializes these services + Rooms *RoomService + VideoRecordings *VideoRecordingService } const defaultTimeout = 30*time.Second + 500*time.Millisecond @@ -245,6 +255,15 @@ func NewLookupClient(accountSid string, authToken string, httpClient *http.Clien return c } +// NewVideoClient returns a new Client to use the video API +func NewVideoClient(accountSid string, authToken string, httpClient *http.Client) *Client { + c := newNewClient(accountSid, authToken, VideoBaseUrl, httpClient) + c.APIVersion = VideoVersion + c.Rooms = &RoomService{client: c} + c.VideoRecordings = &VideoRecordingService{client: c} + return c +} + // NewClient creates a Client for interacting with the Twilio API. This is the // main entrypoint for API interactions; view the methods on the subresources // for more information. @@ -269,6 +288,7 @@ func NewClient(accountSid string, authToken string, httpClient *http.Client) *Cl c.Wireless = NewWirelessClient(accountSid, authToken, httpClient) c.Notify = NewNotifyClient(accountSid, authToken, httpClient) c.Lookup = NewLookupClient(accountSid, authToken, httpClient) + c.Video = NewVideoClient(accountSid, authToken, httpClient) c.Accounts = &AccountService{client: c} c.Applications = &ApplicationService{client: c} diff --git a/responses_test.go b/responses_test.go index 35f5c2b..ce865cc 100644 --- a/responses_test.go +++ b/responses_test.go @@ -70,6 +70,7 @@ func getServer(response []byte) (*Client, *Server) { client.Wireless.Base = s.URL client.Notify.Base = s.URL client.Lookup.Base = s.URL + client.Video.Base = s.URL return client, s } @@ -83,6 +84,7 @@ func getServerCode(response []byte, code int) (*Client, *Server) { client.Wireless.Base = s.URL client.Notify.Base = s.URL client.Lookup.Base = s.URL + client.Video.Base = s.URL return client, s } @@ -2730,5 +2732,50 @@ var phoneLookupResponse = []byte(` } `) +var roomResponse = []byte(` +{ + "api_key_sid": "AC58f1e8f2b1c6b88ca90a012a4be0c279", + "date_created": "2015-07-30T20:00:00Z", + "date_updated": "2015-07-30T20:00:00Z", + "status": "in-progress", + "type": "peer-to-peer", + "sid": "RMca86cf94c7d4f89e0bd45bfa7d9b9e7d", + "enable_turn": false, + "unique_name": "DailyStandup", + "max_participants": 10, + "duration": 0, + "status_callback_method": "POST", + "status_callback": "", + "record_participants_on_connect": false, + "end_time": "2015-07-30T20:00:00Z", + "url": "https://video.twilio.com/v1/Rooms/RMca86cf94c7d4f89e0bd45bfa7d9b9e7d", + "links": { + "recordings": "https://video.twilio.com/v1/Rooms/RMca86cf94c7d4f89e0bd45bfa7d9b9e7d/Recordings" + } +} +`) + +var videoRecordingResponse = []byte(` +{ + "api_key_sid": "AC58f1e8f2b1c6b88ca90a012a4be0c279", + "status": "processing", + "date_created": "2015-07-30T20:00:00Z", + "sid": "RT63868a235fc1cf3987e6a2b67346273f", + "source_sid": "MT58f1e8f2b1c6b88ca90a012a4be0c279", + "size": 0, + "url": "https://video.twilio.com/v1/Recordings/RT58f1e8f2b1c6b88ca90a012a4be0c279", + "type": "audio", + "duration": 20, + "container_format": "mka", + "codec": "OPUS", + "grouping_sids": { + "room_sid" : "RM58f1e8f2b1c6b88ca90a012a4be0c279" + }, + "links": { + "media": "https://video.twilio.com/v1/Recordings/RT58f1e8f2b1c6b88ca90a012a4be0c279/Media" + } +} +`) + const from = "+19253920364" const to = "+19253920364" diff --git a/room.go b/room.go new file mode 100644 index 0000000..92dfdc9 --- /dev/null +++ b/room.go @@ -0,0 +1,92 @@ +package twilio + +import ( + "context" + "net/url" +) + +const roomPathPart = "Rooms" + +type RoomService struct { + client *Client +} + +type Room struct { + Sid string `json:"sid"` + AccountSid string `json:"account_sid"` + Type string `json:"type"` + EnableTurn bool `json:"enable_turn"` + UniqueName string `json:"unique_name"` + StatusCallback string `json:"status_callback"` + StatusCallbackMethod string `json:"status_callback_method"` + MaxParticipants uint `json:"max_participants"` + RecordParticipantsOnConnect bool `json:"record_participants_on_connect"` + Duration uint `json:"duration"` + MediaRegion string `json:"media_region"` + Status Status `json:"status"` + DateCreated TwilioTime `json:"date_created"` + DateUpdated TwilioTime `json:"date_updated"` + EndTime TwilioTime `json:"end_time"` + URL string `json:"url"` + Links map[string]string `json:"links"` +} + +type RoomPage struct { + Meta Meta `json:"meta"` + Rooms []*Room `json:"rooms"` +} + +type RoomPageIterator struct { + p *PageIterator +} + +// Get finds a single Room resource by its sid or unique name, or returns an error. +func (r *RoomService) Get(ctx context.Context, sidOrUniqueName string) (*Room, error) { + room := new(Room) + err := r.client.GetResource(ctx, roomPathPart, sidOrUniqueName, room) + return room, err +} + +// Complete an in-progress Room with the given sid. All connected +// Participants will be immediately disconnected from the Room. +func (r *RoomService) Complete(sid string) (*Room, error) { + room := new(Room) + v := url.Values{} + v.Set("Status", string(StatusCompleted)) + err := r.client.UpdateResource(context.Background(), roomPathPart, sid, v, room) + return room, err +} + +// Create a room with the given url.Values. For more information on valid values, +// see https://www.twilio.com/docs/api/video/rooms-resource#post-parameters or use the +func (r *RoomService) Create(ctx context.Context, data url.Values) (*Room, error) { + room := new(Room) + err := r.client.CreateResource(ctx, roomPathPart, data, room) + return room, err +} + +// Returns a list of rooms. For more information on valid values, +// see https://www.twilio.com/docs/api/video/rooms-resource#get-list-resource +func (r *RoomService) GetPage(ctx context.Context, data url.Values) (*RoomPage, error) { + return r.GetPageIterator(data).Next(ctx) +} + +// GetPageIterator returns an iterator which can be used to retrieve pages. +func (r *RoomService) GetPageIterator(data url.Values) *RoomPageIterator { + iter := NewPageIterator(r.client, data, roomPathPart) + return &RoomPageIterator{ + p: iter, + } +} + +// Next returns the next page of resources. If there are no more resources, +// NoMoreResults is returned. +func (r *RoomPageIterator) Next(ctx context.Context) (*RoomPage, error) { + rp := new(RoomPage) + err := r.p.Next(ctx, rp) + if err != nil { + return nil, err + } + r.p.SetNextPageURI(rp.Meta.NextPageURL) + return rp, nil +} diff --git a/room_test.go b/room_test.go new file mode 100644 index 0000000..6f7112d --- /dev/null +++ b/room_test.go @@ -0,0 +1,25 @@ +package twilio + +import ( + "context" + "testing" +) + +func TestGetRoom(t *testing.T) { + t.Parallel() + client, server := getServer(roomResponse) + defer server.Close() + room, err := client.Video.Rooms.Get(context.Background(), "RMca86cf94c7d4f89e0bd45bfa7d9b9e7d") + if err != nil { + t.Fatal(err) + } + if room.Sid != "RMca86cf94c7d4f89e0bd45bfa7d9b9e7d" { + t.Errorf("room: got sid %q, want %q", room.Sid, "RMca86cf94c7d4f89e0bd45bfa7d9b9e7d") + } + if room.Status != StatusInProgress { + t.Errorf("room: got status %q, want %q", room.Status, StatusInProgress) + } + if room.Type != RoomTypePeerToPeer { + t.Errorf("room: got type %q, want %q", room.Type, RoomTypePeerToPeer) + } +} diff --git a/types.go b/types.go index baa9b77..7959eb9 100644 --- a/types.go +++ b/types.go @@ -328,3 +328,7 @@ func capitalize(s string) string { utf8.EncodeRune(b, unicode.ToTitle(r)) return strings.Join([]string{string(b), s[l:]}, "") } + +// types of video room +const RoomType = "group" +const RoomTypePeerToPeer = "peer-to-peer" diff --git a/video_recording.go b/video_recording.go new file mode 100644 index 0000000..01cfb7e --- /dev/null +++ b/video_recording.go @@ -0,0 +1,95 @@ +package twilio + +import ( + "context" + "net/url" + "strings" +) + +type VideoRecordingService struct { + client *Client +} + +const videoRecordingsPathPart = "Recordings" + +func videoMediaPathPart(recordingSid string) string { + return strings.Join([]string{videoRecordingsPathPart, recordingSid, "Media"}, "/") +} + +type VideoRecording struct { + Sid string `json:"sid"` + Duration uint `json:"duration"` + Status Status `json:"status"` + DateCreated TwilioTime `json:"date_created"` + SourceSid string `json:"source_sid"` + URI string `json:"uri"` + Size uint `json:"size"` + Type string `json:"type"` + ContainerFormat string `json:"container_format"` + Codec string `json:"codec"` + GroupingSids map[string]string `json:"grouping_sids"` + Links map[string]string `json:"links"` +} + +type VideoMedia struct { + Location string `json:"location"` +} + +type VideoRecordingPage struct { + Meta Meta `json:"meta"` + Recordings []*VideoRecording `json:"recordings"` +} + +type VideoRecordingPageIterator struct { + p *PageIterator +} + +// When you make a request to this URL, Twilio will generate a temporary URL for accessing +// this binary data, and issue an HTTP 302 redirect response to your request. The Recording +// will be returned in the format as described in the metadata. +func (vr *VideoRecordingService) Media(ctx context.Context, sid string) (*VideoMedia, error) { + media := new(VideoMedia) + path := videoMediaPathPart(sid) + err := vr.client.ListResource(ctx, path, nil, media) + return media, err +} + +// Returns the VideoRecording with the given sid. +func (vr *VideoRecordingService) Get(ctx context.Context, sid string) (*VideoRecording, error) { + recording := new(VideoRecording) + err := vr.client.GetResource(ctx, videoRecordingsPathPart, sid, recording) + return recording, err +} + +// Delete the VideoRecording with the given sid. If the VideoRecording has already been +// deleted, or does not exist, Delete returns nil. If another error or a +// timeout occurs, the error is returned. +func (vr *VideoRecordingService) Delete(ctx context.Context, sid string) error { + return vr.client.DeleteResource(ctx, videoRecordingsPathPart, sid) +} + +// Returns a list of recordings. For more information on valid values, +// see https://www.twilio.com/docs/api/video/recordings-resource#recordings-list-resource +func (vr *VideoRecordingService) GetPage(ctx context.Context, data url.Values) (*VideoRecordingPage, error) { + return vr.GetPageIterator(data).Next(ctx) +} + +// GetPageIterator returns an iterator which can be used to retrieve pages. +func (vr *VideoRecordingService) GetPageIterator(data url.Values) *VideoRecordingPageIterator { + iter := NewPageIterator(vr.client, data, videoRecordingsPathPart) + return &VideoRecordingPageIterator{ + p: iter, + } +} + +// Next returns the next page of resources. If there are no more resources, +// NoMoreResults is returned. +func (vr *VideoRecordingPageIterator) Next(ctx context.Context) (*VideoRecordingPage, error) { + vrp := new(VideoRecordingPage) + err := vr.p.Next(ctx, vrp) + if err != nil { + return nil, err + } + vr.p.SetNextPageURI(vrp.Meta.NextPageURL) + return vrp, nil +} diff --git a/video_recording_test.go b/video_recording_test.go new file mode 100644 index 0000000..2b46f78 --- /dev/null +++ b/video_recording_test.go @@ -0,0 +1,30 @@ +package twilio + +import ( + "context" + "testing" +) + +func TestGetVideoRecording(t *testing.T) { + t.Parallel() + client, server := getServer(videoRecordingResponse) + defer server.Close() + recording, err := client.Video.VideoRecordings.Get(context.Background(), "RT63868a235fc1cf3987e6a2b67346273f") + if err != nil { + t.Fatal(err) + } + if recording.Sid != "RT63868a235fc1cf3987e6a2b67346273f" { + t.Errorf("recording: got sid %q, want %q", recording.Sid, "RT63868a235fc1cf3987e6a2b67346273f") + } + if recording.Status != StatusProcessing { + t.Errorf("recording: got status %q, want %q", recording.Status, StatusProcessing) + } + for key, value := range recording.GroupingSids { + if key != "room_sid" { + t.Errorf("recording.GroupungSids: got key %q, want %q", key, "room_sid") + } + if value != "RM58f1e8f2b1c6b88ca90a012a4be0c279" { + t.Errorf("recording.GroupungSids: got value %q, want %q", value, "RM58f1e8f2b1c6b88ca90a012a4be0c279") + } + } +}