diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8e44ef6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 JP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/twilio.go b/twilio.go index 73150e2..b4b4ce1 100644 --- a/twilio.go +++ b/twilio.go @@ -99,7 +99,7 @@ func (c *Client) SetMute(ctx context.Context, conferenceSid, callSid string, mut return nil } -// CallResource receives call resource details +// CallResource retrieves call details func (c *Client) CallResource(ctx context.Context, callSid string) (*CallResource, error) { ctx, span := trace.StartSpan(ctx, "twilio.Client.CallResource()") defer span.End() @@ -166,3 +166,67 @@ func (c *Client) Call(ctx context.Context, call *Call) (*CallResource, error) { return callResource, nil } + +// ConferenceResource retrieves conference details +func (c *Client) ConferenceResource(ctx context.Context, conferenceSid string) (*ConferenceResource, error) { + ctx, span := trace.StartSpan(ctx, "twilio.Client.ConferenceResource()") + defer span.End() + + url := fmt.Sprintf("%s/Accounts/%s/Conferences/%s.json", baseURL, c.accountSid, conferenceSid) + + req, err := c.newRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, errors.WithMessage(err, "twilio.Client.ConferenceResource()") + } + + res, err := c.httpClient.Do(req) + if err != nil { + return nil, errors.WithMessage(err, "twilio.Client.ConferenceResource(): http.Do(") + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return nil, errors.WithMessage(decodeError(res.Body), "twilio.Client.ConferenceResource()") + } + + conferencecResource := &ConferenceResource{} + + if err := json.NewDecoder(res.Body).Decode(conferencecResource); err != nil { + return nil, errors.WithMessage(err, "twilio.Client.ConferenceResource(): json.Decoder.Decode()") + } + + return conferencecResource, nil +} + +// ParticipantResource retrieves participant details +func (c *Client) ParticipantResources(ctx context.Context, conferenceSid string) ([]ParticipantResource, error) { + ctx, span := trace.StartSpan(ctx, "twilio.Client.ParticipantResource()") + defer span.End() + + url := fmt.Sprintf("%s/Accounts/%s/Conferences/%s/Participants.json", baseURL, c.accountSid, conferenceSid) + + req, err := c.newRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, errors.WithMessage(err, "twilio.Client.ParticipantResource()") + } + + res, err := c.httpClient.Do(req) + if err != nil { + return nil, errors.WithMessage(err, "twilio.Client.ParticipantResource(): http.Do(") + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return nil, errors.WithMessage(decodeError(res.Body), "twilio.Client.ParticipantResource()") + } + + resource := &struct { + Participants []ParticipantResource + }{} + + if err := json.NewDecoder(res.Body).Decode(resource); err != nil { + return nil, errors.WithMessage(err, "twilio.Client.ParticipantResource(): json.Decoder.Decode()") + } + + return resource.Participants, nil +} diff --git a/types.go b/types.go index 54e2240..910bd43 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,8 @@ package twilio import ( "fmt" + "strings" + "time" ) // CallResource holds the details of a call resouce @@ -12,8 +14,8 @@ type CallResource struct { AnsweredBy string `json:"answered_by,omitempty"` APIVersion string `json:"api_version,omitempty"` CallerName string `json:"caller_name,omitempty"` - DateCreated string `json:"date_created,omitempty"` - DateUpdated string `json:"date_updated,omitempty"` + DateCreated TwilioTime `json:"date_created,omitempty"` + DateUpdated TwilioTime `json:"date_updated,omitempty"` Direction string `json:"direction,omitempty"` Duration string `json:"duration,omitempty"` EndTime string `json:"end_time,omitempty"` @@ -44,7 +46,7 @@ type SubresourceUris struct { Payments string `json:"payments,omitempty"` } -// Call describes a outgoing call settings +// Call describes outgoing call settings type Call struct { AccountSid string `url:"AccountSid,omitempty"` ApplicationSid string `url:"ApplicationSid,omitempty"` @@ -81,6 +83,40 @@ type Call struct { URL string `url:"Url,omitempty"` } +// ConferenceResource holds the details of a conference +type ConferenceResource struct { + AccountSid string `json:"account_sid,omitempty"` + DateCreated TwilioTime `json:"date_created,omitempty"` + DateUpdated TwilioTime `json:"date_updated,omitempty"` + ApiVersion string `json:"api_version,omitempty"` + FriendlyName string `json:"friendly_name,omitempty"` + Region string `json:"region,omitempty"` + Sid string `json:"sid,omitempty"` + Status string `json:"status,omitempty"` // init, in-progress, or completed. + Uri string `json:"uri,omitempty"` + SubresourceUris map[string]string `json:"subresource_uris,omitempty"` + ReasonConferenceEnded string `json:"reason_conference_ended,omitempty"` // conference-ended-via-api, participant-with-end-conference-on-exit-left, participant-with-end-conference-on-exit-kicked, last-participant-kicked, or last-participant-left. + CallSidEndingConference string `json:"call_sid_ending_conference,omitempty"` +} + +// ParticipantResource holds the details of a participant +type ParticipantResource struct { + AccountSid string `json:"account_sid,omitempty"` + CallSid string `json:"call_sid,omitempty"` + CallSidToCoach string `json:"call_sid_to_coach,omitempty"` + Coaching bool `json:"coaching,omitempty"` + ConferenceSid string `json:"conference_sid,omitempty"` + DateCreated TwilioTime `json:"date_created,omitempty"` + DateUpdated TwilioTime `json:"date_updated,omitempty"` + EndConferenceOnExit bool `json:"end_conference_on_exit,omitempty"` + Muted bool `json:"muted,omitempty"` + Hold bool `json:"hold,omitempty"` + StartConferenceOnEnter bool `json:"start_conference_on_enter,omitempty"` + Status string `json:"status,omitempty"` // queued, connecting, ringing, connected, complete, or failed + Uri string `json:"uri,omitempty"` +} + +// APIError holds the details of errors returned from twilio type APIError struct { Code int `json:"code"` Message string `json:"message"` @@ -88,6 +124,33 @@ type APIError struct { Status int `json:"status"` } +// Error returns string representation of the error func (a *APIError) Error() string { return fmt.Sprintf("APIError: %s: more_info: %s", a.Message, a.MoreInfo) } + +// TwilioTime implements interfaces for json Marshalling and Unmarshalling +type TwilioTime struct { + time.Time +} + +const ttLayout = "Mon, 02 Jan 2006 15:04:05 -0700" // 2006/01/02|15:04:05 + +// UnmarshalJSON implements the Unmarshaler interface +func (tt *TwilioTime) UnmarshalJSON(b []byte) (err error) { + s := strings.Trim(string(b), "\"") + if s == "null" { + tt.Time = time.Time{} + return + } + tt.Time, err = time.Parse(ttLayout, s) + return +} + +// MarshslJSON implements the Marshaler interface +func (tt *TwilioTime) MarshalJSON() ([]byte, error) { + if tt.Time.IsZero() { + return []byte("null"), nil + } + return []byte(fmt.Sprintf("\"%s\"", tt.Time.Format(ttLayout))), nil +}