D言語でQiita API

# はじめに
**D言語**での`Qiita API`の使用例を示したいと思います。
`Qiita API`や他の`API`を利用する際にも必要になる、`libcurl`や`JSON`処理の参考になればと思います。

# 今回使った Qiita API
`GET /api/v2/items`を使用しました。
[Qiita API v2ドキュメント](https://qiita.com/api/v2/docs)より、抜粋します。
>GET /api/v2/items
>記事の一覧を作成日時の降順で返します。
>
    page
        ページ番号 (1から100まで)
        Example: 1
        Type: string
        Pattern: /^[0-9]+$/
    per_page
        1ページあたりに含まれる要素数 (1から100まで)
        Example: 20
        Type: string
        Pattern: /^[0-9]+$/
    query
        検索クエリ
        Example: "qiita user:Qiita"
        Type: string

`API`から得られる`JSON`の戻り値は、以下のイメージです。

```json:
[
	{
		"rendered_body":"",
		"body":"",
		"coediting":"",
		"comments_count":"",
		"created_at":"",
		"group":"",
		"id":"",
		"likes_count":"",
		"private":"",
		"reactions_count":"",
		"tags":[{"name":"", "versions":[]}]
		"title":"",
		"updated_at":"",
		"url":"",
		"user":
		{
			"description":""
			"facebook_id":""
			"followees_count":
			"followers_count":
			"github_login_name":""
			"id":""
			"items_count":
			"linkedin_id":""
			"location":""
			"name":""
			"organization":""
			"permanent_id":""
			"profile_image_url":""
			"team_only":
			"twitter_screen_name":""
			"website_url":""
		},
		"page_views_count":""
	}
]
```

# 事前準備（JSON処理の補足説明）

`JSON`形式をパターン分けて、実装例を書いてみました。
これらのパターンを組み合わせれば、いろんな`JSON`形式を処理できます。

```d:json1.d
import std.json;
import std.stdio;

void main()
{
	string s = `{ "name" : "black", "value" : 0 }`;
	JSONValue j = parseJSON(s);
	writeln(s);
	writeln(j["name"].type);	// string
	writeln(j["name"]);			// "black"
	writeln(j["value"].type);	// integer
	writeln(j["value"]);		// 0
	writeln();
	
	s = `{ "key1" : { "key2" : "value" } }`;
	j = parseJSON(s);
	writeln(s);
	writeln(j["key1"].type);	// object
	writeln(j["key1"]["key2"]);	// "value"
	writeln();
	
	s = `[ { "key" : "value1" }, { "key" : "value2" } ]`;
	j = parseJSON(s);
	writeln(s);
	writeln(j.type);			// array
	JSONValue[] jarr = j.array();
	foreach ( jv ; jarr ){
		writeln(jv["key"]);		// "value1" "value2"
	}
	writeln();
	
	s = `
	{
		"royalblue" : "#4169e1",
		"darkgreen" : "#006400",
		"deeppink"  : "#ff1493"
	}`;
	j = parseJSON(s);
	writeln(s);
	writeln(j.type);			// object
	foreach ( string key, value; j ){
		writefln("%-9s : %s", key, value);	// not sorted
	}
}
```
```text:実行結果
D:\Dev>dmd -m64 json1.d

D:\Dev>json1
{ "name" : "black", "value" : 0 }
string
"black"
integer
0

{ "key1" : { "key2" : "value" } }
object
"value"

[ { "key" : "value1" }, { "key" : "value2" } ]
array
"value1"
"value2"


        {
                "royalblue" : "#4169e1",
                "darkgreen" : "#006400",
                "deeppink"  : "#ff1493"
        }
object
darkgreen : "#006400"
royalblue : "#4169e1"
deeppink  : "#ff1493"
```

１つ目のパターンは、項目名（"name"や"value"）を指定して、値（"black"や0）を得る、基本パターンです。
`parseJSON`を使うことで、`JSON`の解析結果が`JSONValue`に格納されます。
`.type`を使えば、値の型を得ることができます。
実装例では、`string`、`integer`、`object`、`array`が得られています。

２つ目のパターンは、階層構造（`.type`が`object`）になっている場合に値を得る例です。
`j["key1"]["key2"]`という風に項目を指定することができます。

３つ目のパターンは、配列構造（`.type`が`array`）になっている場合に値を得る例です。
実装例では、`JSONValue[] jarr = j.array()`を使って、ループで値を得ています。

４つめのパターンは、仮に項目名がわからなくても、ループ処理`foreach ( string key, value; j )`で項目名、値を得られることを示しています。

# ソースコード
```d:qiitaapi.d
import std.conv;
import std.json;
import std.net.curl;
import std.stdio;
import std.uri;

void main()
{
	listQiitaItems(getQiitaItems("tag:dlang", 5));
	listQiitaItems(getQiitaItems("tag:D言語", 5));
}

JSONValue[] getQiitaItems(string option, int maxpage)
{
	writeln(option);
	if ( option != "" ){
		option = "query=" ~ option.encodeComponent ~ "&";
	}
	string sUrl = "https://qiita.com/api/v2/items?" ~ option ~ "per_page=100&page=";
	writeln(sUrl);
	auto http = HTTP();
	JSONValue[] jarr;
	for ( int i = 1; i <= maxpage; i++ ){
		JSONValue[] j = parseJSON(cast(string)get(sUrl ~ i.to!string, http)).array;
		if ( j.length == 0 ){
			break;
		}
		jarr ~= j;
	}
	return ( jarr );
}

void listQiitaItems(JSONValue[] jarr)
{
	foreach ( i, j; jarr ){
		writefln("No.%3d", i + 1);
		writefln("title  : %s", j["title"]);
		writefln("user-id: %s", j["user"]["id"]);
		write(   "tags   : ");
		foreach ( j1; j["tags"].array ){
			writef("%s ", j1["name"]);
		}
		writeln();
		writefln("LGTM   : %s", j["likes_count"]);
		writefln("comment: %s", j["comments_count"]);
		writeln();
	}
}
```

## getQiitaItems

`getQiitaItems`関数で、`libcurl`を使用しています。
[libcurl get関数](https://dlang.org/phobos/std_net_curl.html#.get)
QiitaAPIでの利用制限を考慮して、`maxpage`を`5`に指定しています。

>利用制限
>認証している状態ではユーザごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。

検索結果は`API`の呼び出し１回ごとに最大100件です。100件を超える場合を想定してループ処理になっています。


# 実行結果
```text:実行結果
D:\Dev>dmd -m64 qiitaapi.d
D:\Dev>qiitaapi > res.txt
```
```text:res.txt
tag:dlang
https://qiita.com/api/v2/items?query=tag%3Adlang&per_page=100&page=
No.  1
title  : "Window10にWSL2+Ubuntuを導入し、GDC、DMD、LDCを使ってみた"
user-id: "devmynote"
tags   : "dlang" "D言語" "Windows10" "WSL2" 
LGTM   : 1
comment: 0

No.  2
title  : "D言語で迷路を再現（ドルアーガの塔）"
user-id: "devmynote"
tags   : "dlang" "D言語" "クロージャ" "乱数" "迷路" 
LGTM   : 1
comment: 0

No.  3
title  : "dub v1.23.0のx86_omfについて"
user-id: "mono_shoo"
tags   : "dlang" 
LGTM   : 5
comment: 0

・・・　省略　・・・

No.399
title  : "template for getting a field name of class or struct"
user-id: "repeatedly"
tags   : "Dlanguage" "dlang" 
LGTM   : 1
comment: 0

tag:D言語
https://qiita.com/api/v2/items?query=tag%3AD%E8%A8%80%E8%AA%9E&per_page=100&page=
No.  1
title  : "Window10にWSL2+Ubuntuを導入し、GDC、DMD、LDCを使ってみた"
user-id: "devmynote"
tags   : "dlang" "D言語" "Windows10" "WSL2" 
LGTM   : 1
comment: 0

・・・　省略　・・・

No. 99
title  : "D言語のビルドツール DUB"
user-id: "kazoo04"
tags   : "D言語" 
LGTM   : 20
comment: 0

```

この記事を投稿する前の実行結果となります。
この記事が`tag:dlang`で400番目の、`tag:D言語`で100番目の記事になる予定です。
キリ番ゲットしちゃいました。:wink:

# 参考情報

[Qiita API v2ドキュメント](https://qiita.com/api/v2/docs)
[std.net.curl 使用例](https://dlang-jp.github.io/Cookbook/cookbook--network_example.html)
[std.json 使用例](https://dlang-jp.github.io/Cookbook/cookbook--data.json_example.html)

## D言語 Library Reference
[std.net.curl(英語)](https://dlang.org/phobos/std_net_curl.html)
[std.json(英語)](https://dlang.org/phobos/std_json.html)