32
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AbbyAdvent Calendar 2014

Day 3

C言語でtwitterにPOSTする

Posted at

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

liboauth

C言語でtwitter APIを利用するには liboauth を利用する。

本当はもっと楽に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言語からでも利用できる。以下はフォローしているユーザ一覧の取得例(断片のみ)。
クエリ文字列として各種パラメータを渡せばいいだけ。


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 にも置いておいた。

#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 で分離するあたり、かなり残念な感じがするのでもっとストレートなやり方がありそうな気がする。

参考

32
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?