Skip to content

Commit c2e8c7a

Browse files
committed
feat: support POST /2/media/upload/:mediaID/append
1 parent a9ef999 commit c2e8c7a

4 files changed

Lines changed: 109 additions & 2 deletions

File tree

client.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,13 @@ func newRequest(ctx context.Context, endpoint, method string, p util.Parameters)
378378
return nil, err
379379
}
380380

381-
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
381+
contentType := "application/json;charset=UTF-8"
382+
if ct := ctx.Value("Content-Type"); ct != nil {
383+
if ctStr, ok := ct.(string); ok {
384+
contentType = ctStr
385+
}
386+
}
387+
req.Header.Set("Content-Type", contentType)
382388

383389
return req, nil
384390
}

media/upload/api.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package upload
22

33
import (
44
"context"
5+
"fmt"
56

67
"github.com/michimani/gotwi"
78
"github.com/michimani/gotwi/media/upload/types"
89
)
910

1011
const (
11-
initializeEndpoint = "https://api.twitter.com/2/media/upload/initialize"
12+
initializeEndpoint = "https://api.x.com/2/media/upload/initialize"
13+
appendEndpoint = "https://api.x.com/2/media/upload/:mediaID/append"
1214
)
1315

1416
func Initialize(ctx context.Context, c *gotwi.Client, p *types.InitializeInput) (*types.InitializeOutput, error) {
@@ -19,3 +21,16 @@ func Initialize(ctx context.Context, c *gotwi.Client, p *types.InitializeInput)
1921

2022
return res, nil
2123
}
24+
25+
func Append(ctx context.Context, c *gotwi.Client, p *types.AppendInput) (*types.AppendOutput, error) {
26+
boundary := p.GenerateBoundary()
27+
contentType := fmt.Sprintf("multipart/form-data;charset=UTF-8;boundary=%s", boundary)
28+
29+
res := &types.AppendOutput{}
30+
ctx = context.WithValue(ctx, "Content-Type", contentType)
31+
if err := c.CallAPI(ctx, appendEndpoint, "POST", p, res); err != nil {
32+
return nil, err
33+
}
34+
35+
return res, nil
36+
}

media/upload/types/parameter.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package types
22

33
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"encoding/hex"
47
"encoding/json"
8+
"errors"
59
"io"
10+
"mime/multipart"
11+
"strconv"
612
"strings"
713
)
814

@@ -85,3 +91,72 @@ func (p *InitializeInput) Body() (io.Reader, error) {
8591
func (p *InitializeInput) ParameterMap() map[string]string {
8692
return map[string]string{}
8793
}
94+
95+
type AppendInput struct {
96+
accessToken string
97+
boundary string
98+
99+
// Path parameter: The media identifier for the media to perform the append operation.
100+
MediaID string
101+
102+
// The file to upload.
103+
Media io.Reader
104+
105+
// An integer value representing the media upload segment.
106+
SegmentIndex int
107+
}
108+
109+
func (p *AppendInput) SetAccessToken(token string) {
110+
p.accessToken = token
111+
}
112+
113+
func (p *AppendInput) AccessToken() string {
114+
return p.accessToken
115+
}
116+
117+
func (p *AppendInput) Boundary() string {
118+
return p.boundary
119+
}
120+
121+
func (p *AppendInput) GenerateBoundary() string {
122+
h := sha256.New()
123+
h.Write([]byte(p.MediaID + strconv.Itoa(p.SegmentIndex)))
124+
boundary := hex.EncodeToString(h.Sum(nil))
125+
p.boundary = boundary
126+
return boundary
127+
}
128+
129+
func (p *AppendInput) ResolveEndpoint(endpointBase string) string {
130+
endpoint := strings.Replace(endpointBase, ":mediaID", p.MediaID, 1)
131+
return endpoint
132+
}
133+
134+
func (p *AppendInput) Body() (io.Reader, error) {
135+
body := &bytes.Buffer{}
136+
writer := multipart.NewWriter(body)
137+
defer writer.Close()
138+
139+
if p.boundary == "" {
140+
return nil, errors.New("boundary is not set")
141+
}
142+
writer.SetBoundary(p.boundary)
143+
144+
part, err := writer.CreateFormFile("media", "media")
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
if _, err := io.Copy(part, p.Media); err != nil {
150+
return nil, err
151+
}
152+
153+
if err := writer.WriteField("segment_index", strconv.Itoa(p.SegmentIndex)); err != nil {
154+
return nil, err
155+
}
156+
157+
return body, nil
158+
}
159+
160+
func (p *AppendInput) ParameterMap() map[string]string {
161+
return map[string]string{}
162+
}

media/upload/types/response.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,14 @@ type InitializeOutput struct {
1111
func (r *InitializeOutput) HasPartialError() bool {
1212
return !(r.Errors == nil || len(r.Errors) == 0)
1313
}
14+
15+
type AppendOutput struct {
16+
Data struct {
17+
ExpiresAt int `json:"expires_at"`
18+
} `json:"data"`
19+
Errors []resources.PartialError `json:"errors"`
20+
}
21+
22+
func (r *AppendOutput) HasPartialError() bool {
23+
return !(r.Errors == nil || len(r.Errors) == 0)
24+
}

0 commit comments

Comments
 (0)