iOSからTwitter APIで動画つきツイートする

## 概要

iOSからTwitterへ動画のアップロードを行う。

画像の場合は [media/upload](https://dev.twitter.com/rest/reference/post/media/upload) のAPIの1コールでアップロードできるようだが、動画の場合はエラー(400 bad request)になってしまう。

そのため [media/upload-chunked](https://dev.twitter.com/rest/reference/post/media/upload-chunked) を使う必要がある。

## 動画の制約

アップロードできる動画の条件は以下のとおり。
- MPEG4
- 1ファイルのみ
- ファイルサイズは最大15MB
- 長さは0.5秒から30秒まで
- 大きさは 32x32 から 1280x1024 まで
- 縦横比は 1:3 から 3:1 まで
- フレームレートは 40fps 以下
- 音声はモノラルかステレオ

## SDK

SDK(Fabric TwitterKit)を使う。準備については省略。

## 手順

### ログイン

```objc
// Twitterにログインする
// アカウント選択のActionSheetはSDK側で表示される
// 選択およびログインが完了したらコールバックが呼ばれる
Twitter *twitter = [Twitter sharedInstance];
[twitter logInWithCompletion:^(TWTRSession *session, NSError *error) {
    if (error) {
        return;
    }
    // dataは動画のNSData*で事前に読み込んだもの
    [self twitterMediaUploadInitWithSession:session 
                                       data:data];
}];
```

### INIT

```objc
- (void)twitterMediaUploadInitWithSession:(TWTRSession*)session 
                                     data:(NSData*)data {

// API Client
TWTRAPIClient *client = [[TWTRAPIClient alloc] initWithUserID:session.userID];

// Requestを用意
NSString *url = @"https://upload.twitter.com/1.1/media/upload.json";
NSDictionary *params = @{
    @"command": @"INIT",
    @"total_bytes": [NSString stringWithFormat:@"%d", data.length], // @(data.length) だと死ぬ
    @"media_type": @"video/mp4",
};
NSError* error = nil;
NSURLRequest *request = [client URLRequestWithMethod:@"POST" 
                                                 URL:url 
                                          parameters:params 
                                               error:&error];
if (error) {
    return;
}

// 送信
[client sendTwitterRequest:request completion:^(NSURLResponse *response, NSData *body, NSError *twError) {
    if (twError) {
        return;
    }
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:body 
                                                         options:0 
                                                           error:&twError];
    if (twError) {
        return;
    }

    // レスポンスのJSON内のmedia_id_stringで一連の処理を行う
    [self twitterMediaUploadAppendWithClient:client 
                                        data:data 
                                     mediaId:dict[@"media_id_string"]];
}];

}
```

### APPEND

```objc
- (void)twitterMediaUploadAppendWithClient:(TWTRAPIClient*)client 
                                      data:(NSData*)data 
                                   mediaId:(NSString*)mediaId {

if (!mediaId || [mediaId length] <= 0) {
    return;
}

// Requestを用意
NSString *dataStr = [data base64EncodedStringWithOptions:0];
NSString *url = @"https://upload.twitter.com/1.1/media/upload.json";
NSDictionary *params = @{
    @"command": @"APPEND",
    @"media_id": mediaId,
    @"media_data": dataStr, // BASE64しないと死ぬ
    @"segment_index": @"0", // この例では1回のAPPENDで送信しきるので0
};
NSError* error = nil;
NSURLRequest *request = [client URLRequestWithMethod:@"POST" 
                                                 URL:url 
                                          parameters:params 
                                               error:&error];
if (error) {
    return;
}

// 送信
[client sendTwitterRequest:request completion:^(NSURLResponse *response, NSData *body, NSError *twError) {
    if (twError) {
        return;
    }
    [self twitterMediaUploadFinalizeWithClient:client 
                                       mediaId:mediaId];
}];

}
```

### FINALIZE

```objc
- (void)twitterMediaUploadFinalizeWithClient:(TWTRAPIClient*)client 
                                     mediaId:(NSString*)mediaId {

// Requestを用意
NSString *url = @"https://upload.twitter.com/1.1/media/upload.json";
NSDictionary *params = @{
    @"command": @"FINALIZE",
    @"media_id": mediaId,
};
NSError* error = nil;
NSURLRequest *request = [client URLRequestWithMethod:@"POST" 
                                                 URL:url 
                                          parameters:params 
                                               error:&error];
if (error) {
    return;
}

// 送信
[client sendTwitterRequest:request completion:^(NSURLResponse *response, NSData *body, NSError *twError) {
    if (twError) {
        return;
    }
    [self twitterStatusUpdateWithClient:client 
                                mediaId:mediaId];
}];

}
```

### Tweet

```objc
- (void)twitterStatusUpdateWithClient:(TWTRAPIClient*)client 
                              mediaId:(NSString*)mediaId {

// Requestを用意
NSString *url = @"https://api.twitter.com/1.1/statuses/update.json"
NSDictionary *params = @{
    @"status": @"ツイート文はここに入れる",
    @"media_ids": mediaId,
};
NSError* error = nil;
NSURLRequest *request = [client URLRequestWithMethod:@"POST" 
                                                 URL:url 
                                          parameters:params 
                                               error:&error];
if (error) {
    return;
}

// 送信
[client sendTwitterRequest:request completion:^(NSURLResponse *response, NSData *body, NSError *twError) {
    if (twError) {
        return;
    }
    // 動画つきツイート成功！
}];

}
```