@@ -6,20 +6,10 @@ import (
66 "encoding/json"
77 "fmt"
88 "io"
9- "mime"
109 "net/http"
11- "net/url"
1210 "path"
1311)
1412
15- const (
16- CobaltPublicAPI = "https://api.cobalt.tools/api"
17-
18- EndpointJSON = "/json"
19- EndpointStream = "/stream"
20- EndpointServerInfo = "/serverInfo"
21- )
22-
2313type Cobalt struct {
2414 client * http.Client
2515 apiBaseURL string
@@ -32,41 +22,42 @@ func NewCobaltWithAPI(apiBaseURL string) *Cobalt {
3222 }
3323}
3424
35- func NewCobaltWithPublicAPI () * Cobalt {
36- return & Cobalt {
37- client : http .DefaultClient ,
38- apiBaseURL : CobaltPublicAPI ,
39- }
40- }
41-
4225func (c * Cobalt ) WithHTTPClient (client * http.Client ) * Cobalt {
4326 c .client = client
4427 return c
4528}
4629
47- // Get will return a Response from where the file can be downloaded
48- func (c * Cobalt ) Get (ctx context.Context , params Request ) (* Media , error ) {
30+ // Post will return a PostResponse from where the file can be downloaded
31+ // headers are passed as key value pairs. Examples `"API-KEY", "MyApiKey"`
32+ func (c * Cobalt ) Post (ctx context.Context , params PostRequest , headers ... string ) (* PostResponse , error ) {
4933 buff := & bytes.Buffer {}
5034 if err := json .NewEncoder (buff ).Encode (params ); err != nil {
5135 return nil , err
5236 }
5337
54- u := fmt .Sprintf ("%s%s" , c .apiBaseURL , EndpointJSON )
55- req , err := http .NewRequestWithContext (ctx , http .MethodPost , u , buff )
38+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , c .apiBaseURL , buff )
5639 if err != nil {
5740 return nil , err
5841 }
5942
6043 req .Header .Add ("Content-Type" , "application/json" )
6144 req .Header .Add ("Accept" , "application/json" )
6245
46+ if len (headers )% 2 != 0 {
47+ return nil , fmt .Errorf ("odd number of headers params, they must be passed as key value pairs" )
48+ }
49+
50+ for i := 0 ; i < len (headers ); i += 2 {
51+ req .Header .Add (headers [i ], headers [i + 1 ])
52+ }
53+
6354 resp , err := c .client .Do (req )
6455 if err != nil {
6556 return nil , err
6657 }
6758 defer resp .Body .Close ()
6859
69- media := & Media {client : c .client }
60+ media := & PostResponse {client : c .client }
7061 if err := json .NewDecoder (resp .Body ).Decode (media ); err != nil {
7162 return nil , err
7263 }
@@ -78,75 +69,108 @@ func (c *Cobalt) Get(ctx context.Context, params Request) (*Media, error) {
7869 return media , nil
7970}
8071
81- // ParseFilename will try to extract the filename depending on the type of the m.StatusResponse.
82- // This is intended to be used with the *http.Response when calling the URL pointedb by m.URL
83- //
84- // When m.StatusResponse == StatusResponseRedirect, the filename will be set based on the basename of the URL path
85- //
86- // When m.StatusResponse == StatusResponseStream, the filename will be extracted from the Content-Disposition header
87- //
88- // All other unsupported methods leave the m.Filename empty
89- // Errors returned are unexpected, and will be a consenquence of a parsing error.
90- func (m * Media ) ParseFilename (resp * http.Response ) error {
91- if m .Status == ResponseStatusError || m .Status == ResponseStatusRateLimit {
92- return nil
93- }
94-
95- if m .Status == ResponseStatusRedirect {
96- parsedURL , err := url .Parse (m .URL )
97- if err != nil {
98- return err
99- }
100- m .filename = path .Base (parsedURL .Path )
101- return nil
102- }
103-
104- if m .Status == ResponseStatusStream {
105- cd := resp .Header .Get ("Content-Disposition" )
106- if cd != "" {
107- _ , params , err := mime .ParseMediaType (cd )
108- if err != nil {
109- return err
110- }
111- if filename , ok := params ["filename" ]; ok {
112- m .filename = filename
113- }
114- }
115- }
116-
117- return nil
72+ // Stream is a helper utility that will return an io.ReadCloser using the URL from this media object
73+ // The returned io.ReadCloser is the Body of *http.Response and must be closed when you are done with the stream.
74+ // When the m.Status == ResponseStatusPicker it will stream the first item from the m.Picker array.
75+ func (m * PostResponse ) Stream (ctx context.Context ) (io.ReadCloser , error ) {
76+ if m .Status != ResponseStatusTunnel && m .Status != ResponseStatusRedirect && m .Status != ResponseStatusPicker {
77+ return nil , fmt .Errorf ("unstreamable response type %s" , m .Status )
78+ }
79+
80+ url := m .URL
81+ if m .Status == ResponseStatusPicker && len (m .Picker ) > 0 {
82+ url = m .Picker [0 ].URL
83+ }
84+ if len (url ) == 0 {
85+ return nil , fmt .Errorf ("url is empty, nothing to stream" )
86+ }
87+
88+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , url , nil )
89+ if err != nil {
90+ return nil , err
91+ }
92+
93+ resp , err := m .client .Do (req )
94+ if err != nil {
95+ return nil , err
96+ }
97+
98+ return resp .Body , nil
11899}
119100
120- // Filename will return the filename associated with this media. ParseFilename must be called first, either directly or indirectly via m.Stream().
121- // Not doing so will keep the filename empty.
122- func (m * Media ) Filename () string {
123- return m .filename
101+ func (c * Cobalt ) Get (ctx context.Context , headers ... string ) (* GetResponse , error ) {
102+
103+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , c .apiBaseURL , nil )
104+ if err != nil {
105+ return nil , err
106+ }
107+
108+ req .Header .Add ("Accept" , "application/json" )
109+
110+ if len (headers )% 2 != 0 {
111+ return nil , fmt .Errorf ("odd number of headers params, they must be passed as key value pairs" )
112+ }
113+
114+ for i := 0 ; i < len (headers ); i += 2 {
115+ req .Header .Add (headers [i ], headers [i + 1 ])
116+ }
117+
118+ resp , err := c .client .Do (req )
119+ if err != nil {
120+ return nil , err
121+ }
122+ defer resp .Body .Close ()
123+
124+ info := & GetResponse {}
125+ if err := json .NewDecoder (resp .Body ).Decode (info ); err != nil {
126+ return nil , err
127+ }
128+
129+ return info , nil
124130}
125131
126- // Stream is a helper utility that will return an io.ReadCloser using the URL from this media object
127- // The returned io.ReadCloser is the Body of *http.Response and must be closed when you are done with the stream.
128- // Stream will also call ParseFilename, so m.Filename() will be set
129- func (m * Media ) Stream (ctx context.Context ) (io.ReadCloser , error ) {
130- req , err := http .NewRequestWithContext (ctx , http .MethodGet , m .URL , nil )
132+ const (
133+ EndpointSession = "session"
134+ )
135+
136+ func (c * Cobalt ) Session (ctx context.Context , headers ... string ) (* SessionResponse , error ) {
137+
138+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , path .Join (c .apiBaseURL , EndpointSession ), nil )
131139 if err != nil {
132140 return nil , err
133141 }
134142
135- resp , err := m .client .Do (req )
143+ req .Header .Add ("Accept" , "application/json" )
144+
145+ if len (headers )% 2 != 0 {
146+ return nil , fmt .Errorf ("odd number of headers params, they must be passed as key value pairs" )
147+ }
148+
149+ for i := 0 ; i < len (headers ); i += 2 {
150+ req .Header .Add (headers [i ], headers [i + 1 ])
151+ }
152+
153+ resp , err := c .client .Do (req )
136154 if err != nil {
137155 return nil , err
138156 }
139- if err := m .ParseFilename (resp ); err != nil {
140- defer resp .Body .Close ()
157+ defer resp .Body .Close ()
158+
159+ token := & SessionResponse {}
160+ if err := json .NewDecoder (resp .Body ).Decode (token ); err != nil {
141161 return nil , err
142162 }
143163
144- return resp .Body , nil
164+ if token .Status == ResponseStatusError {
165+ return nil , fmt .Errorf ("%+v" , token .ErrorInfo )
166+ }
167+
168+ return token , nil
145169}
146170
147171// CobalAPIError is just a convenient type to convert Media into an error.
148- type CobaltAPIError Media
172+ type CobaltAPIError PostResponse
149173
150174func (err CobaltAPIError ) Error () string {
151- return err .Text
175+ return fmt . Sprintf ( "%+v" , err .ErrorInfo )
152176}
0 commit comments