C言語でtwitterにPOSTする

Cでtwitter APIを叩くサンプル。
関数名とか変数名とか雑で、いろいろややこしい感じになっているが、そのへんはC初心者ということで大目に見ていただきたい。
gnu99前提。

# liboauth
C言語でtwitter APIを利用するには **[liboauth](http://liboauth.sourceforge.net/)** を利用する。

本当はもっと楽にtwitterのAPIを叩けるライブラリがあるといいのだが、 https://dev.twitter.com/overview/api/twitter-libraries のC言語の箇所にはliboauthしか載っていないので、今のところtwitter専用の定番ライブラリみたいなものはないのかもしれない。

http自体はlibcurlを使う。liboauth内蔵クライアントも一応あるっぽいがたいていdeprecatedなので使わないほうがいい。

# GET
https://dev.twitter.com/rest/public にあるAPIのうち、GETリクエストで利用できるものは比較的簡単にC言語からでも利用できる。以下はフォローしているユーザ一覧の取得例(断片のみ)。
クエリ文字列として各種パラメータを渡せばいいだけ。

```c

static int send_get_request(CURL *handle, const char *url) {
    int r = curl_easy_setopt(handle, CURLOPT_URL, url);
    if (r != CURLE_OK) {
        return r;
    }
    r = curl_easy_perform(handle);
    if (r != CURLE_OK) {
        return r;
    }
    //Check response code
    long rc = 0;
    r = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &rc);
    if (r != CURLE_OK) {
        return r;
    }
    if (rc / 100 != 2) {
        return -1;
    }

    return 0;
}

static int get_user_id_list() {
    char *url = oauth_sign_url2(
                    "https://api.twitter.com/1.1/friends/ids.json",
                    NULL,
                    OA_HMAC,
                    "GET",
                    "consumer_key",
                    "consumer_secret",
                    "access_token",
                    "access_token_secret"
                );
    if (url == NULL) {
        return -1;
    }

    CURL *handle = curl_easy_init();
    if (handle == NULL) {
        free(url);
        return -1;
    }
    int r = send_get_request(handle, url);

    //Free and clean up
    curl_easy_cleanup(handle);
    free(url);
    return  r;
}
```

# POST
POSTリクエストで利用するAPIはクエリ文字列ではなくHTTP リクエストヘッダとしてパラメータを渡す必要がある。リクエストボディにもAPIに渡すパラメータをもたせる必要がある。

以下の例ではAPIに渡すパラメータはstatusのみだが、DM送信など2つ以上指定しないと利用できないAPIもある。その場合パラメータの区切り文字'?'をURLエンコードしないように注意しつつ、 ``create_encoded_tweet`` を書き換える必要がある。

[github](https://github.com/garsue/twitter-c-sample) にも置いておいた。

```c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <oauth.h>
#include <curl/curl.h>

#define CONTENT_SIZE 141
#define TWEET_URL "https://api.twitter.com/1.1/statuses/update.json"
#define KEY_STATUS "status="

typedef struct {
    char *content;
    char *url_enc_args;
    char *postdata;
    int argc;
    char **argv;
    struct curl_slist *header;
} twitter;

static int create_twitter(twitter *self) {
    //Initialize instance
    *self = (twitter) {
        .content = "test tweet",
        .url_enc_args = NULL,
        .argc = 0,
        .argv = malloc(0),
        .header = NULL,
        .postdata = NULL,
    };

    if (self->argv == NULL) {
        return -1;
    }

    return 0;
}

static int create_base_arg(twitter *self, const char *url) {
    size_t ser_url_size = strlen(url) + strlen(self->url_enc_args) + 2;
    char ser_url[ser_url_size];
    int r = snprintf(ser_url, ser_url_size, "%s?%s", url, self->url_enc_args);
    if (r < 0) {
        return r;
    }
    free(self->url_enc_args);
    self->url_enc_args = NULL;

    //Get user arguments
    self->argc = oauth_split_url_parameters(ser_url, &self->argv);
    //Get OAuth arguments
    char *tmp_url = oauth_sign_array2(
                        &self->argc,
                        &self->argv,
                        NULL,
                        OA_HMAC,
                        "POST",
                        "XXXXXXXXXXXXXXXXXXXXXXXXX",//consumer_key
                        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",//consumer_secret
                        "XXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",//access_token
                        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"//access_token_secret
                    );
    if (tmp_url == NULL) {
        return -1;
    }
    free(tmp_url);

    return 0;
}

static int create_encoded_tweet(twitter *self) {
    size_t size = sizeof KEY_STATUS + CONTENT_SIZE;
    char string[size];
    string[0] = '\0';
    strncat(string, KEY_STATUS, size);
    strncat(string, self->content, size);
    self->url_enc_args = oauth_url_escape(string);
    return self->url_enc_args == NULL ? -1 : 0;
}

static int create_request_header(twitter *self) {
    char *auth_params = oauth_serialize_url_sep(self->argc, 1, self->argv, ", ", 6);
    if (auth_params == NULL) {
        return -1;
    }
    char auth_header[4096];
    sprintf(auth_header, "Authorization: OAuth %s", auth_params);
    free(auth_params);

    self->header = curl_slist_append(self->header, auth_header);
    if (self->header == NULL) {
        return -1;
    }
    return 0;
}

static int create_postdata(twitter *self) {
    self->postdata = oauth_serialize_url_sep(self->argc, 1, self->argv, "&", 1);
    if (self->postdata == NULL) {
        return -1;
    }
    return 0;
}

static int send_post_request(CURL *handle, twitter *self, const char *url,
                             size_t (*write_callback)(char *ptr, size_t size, size_t nmemb, void *userdata)) {
    int r = 0;

    //Set postdata
    r = curl_easy_setopt(handle, CURLOPT_POSTFIELDS, self->postdata);
    if (r != CURLE_OK) {
        return r;
    }
    //Set request header
    r = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, self->header);
    if (r != CURLE_OK) {
        return r;
    }
    r = curl_easy_setopt(handle, CURLOPT_URL, url);
    if (r != CURLE_OK) {
        return r;
    }
    r = curl_easy_setopt(handle, CURLOPT_POST, 1);
    if (r != CURLE_OK) {
        return r;
    }
    r = curl_easy_setopt(handle, CURLOPT_USE_SSL, CURLUSESSL_ALL);
    if (r != CURLE_OK) {
        return r;
    }
    r = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
    if (r != CURLE_OK) {
        return r;
    }
    r = curl_easy_perform(handle);
    if (r != CURLE_OK) {
        return r;
    }
    //Check response code
    long rc = 0;
    r = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &rc);
    if (r != CURLE_OK) {
        return r;
    }
    if (rc / 100 != 2) {
        return -1;
    }

    return 0;
}

static size_t post_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t r = size * nmemb;
    printf("Posted to twitter. Response size: %zu\n", r);
    printf("%s\n", ptr);
    return r;
}

static void free_argv(twitter *self) {
    for (int i = 0; i < self->argc; i++) {
        free(self->argv[i]);
    }
    free(self->argv);
    self->argc = 0;
    self->argv = NULL;
}

static void free_twitter(twitter *self) {
    free_argv(self);
    free(self->url_enc_args);
    free(self->postdata);
    curl_slist_free_all(self->header);
}

static int tweet() {
    twitter t;
    int r = create_twitter(&t);
    if (r != 0) {
        free_twitter(&t);
        return r;
    }

    //Create URL encoded user arguments string
    r = create_encoded_tweet(&t);
    if (r != 0) {
        free_twitter(&t);
        return -1;
    }

    //Create base arg for header and postdata
    r = create_base_arg(&t, TWEET_URL);
    if (r != 0) {
        free_twitter(&t);
        return r;
    }

    //Create request header
    r = create_request_header(&t);
    if (r != 0) {
        free_twitter(&t);
        return r;
    }

    //Create postdata
    r = create_postdata(&t);
    if (r != 0) {
        free_twitter(&t);
        return r;
    }

    //Free OAuth arguments
    free_argv(&t);

    //Tweet with curl
    CURL *handle = curl_easy_init();
    if (handle == NULL) {
        free_twitter(&t);
        return -1;
    }
    r = send_post_request(handle, &t, TWEET_URL, post_callback);

    //Free and clean up
    free_twitter(&t);
    curl_easy_cleanup(handle);

    return r;
}


int main(int argc, char *argv[]) {
    int r = curl_global_init(CURL_GLOBAL_ALL);
    if (r != 0) {
        return EXIT_FAILURE;
    }

    r = tweet();
    curl_global_cleanup();

    return r != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
```

パラメータを一旦クエリ文字列形式にしたあと `oauth_split_url_parameters` で分離するあたり、かなり残念な感じがするのでもっとストレートなやり方がありそうな気がする。

# 参考
https://stackoverflow.com/questions/24539398/post-ing-a-tweet-using-libcurl-and-liboauth-in-c