DartでpixivAPIの非公式ラッパーを作る
概要
pixivAPIの非公式ラッパー。
作る理由
- pixivpyでは返り値がJsonHook(jsonを元に自動生成されたクラス)なため、IDEでプロパティがサジェストされず、使いにくい。
- 開発体験が良く、自分と相性が良いDartでpixivAPIを叩きたい
こうしたい
- modelはfreezedを利用してjsonシリアライズ、toStringなどをサポート
- できる限りAPIをカバー
- 例外をちゃんと投げる
- iOS最新版のAPIをカバー
参考
TODO
- OAuth認証でアクセスコードを取得できるようにする
- リフレッシュトークンでログインできるようにする
- Pixivをインスタンス化、ログインできるようにする
- イラストのダウンロード機能
- レスポンスのステータスコードを確認、例外を投げる関数を作る
- http.Clientを渡せるようにする
APIClientの実装
API
最低限ほしい
-
illust
-
v2/illust/bookmark/add
-
v1/illust/bookmark/delete
-
v2/illust/bookmark/detail
-
v1/illust/detail
-
-
novel
-
v2/novel/bookmark/add
-
v1/novel/bookmark/delete
-
v2/novel/bookmark/detail
-
v2/novel/detail
-
-
user
-
v1/user/bookmarks/illust
-
v1/user/bookmarks/novel
-
v1/user/detail
-
優先的に
-
illust
-
v2/illust/follow
-
v2/illust/mypixiv
-
v1/illust/new
-
v1/illust/ranking
-
v1/illust/recommended
-
v2/illust/related
-
-
novel
-
v1/novel/follow
-
v1/novel/mypixiv
-
v1/novel/new
-
v1/novel/ranking
-
v1/novel/recommended
-
v1/novel/related
-
-
search
-
v1/search/illust
-
v1/search/novel
-
v1/search/user
-
-
user
-
v1/user/follow/add
-
v1/user/follow/delete
-
v1/user/following
-
v1/user/illusts
-
v1/user/novels
-
v1/user/recommended
-
v1/user/related
-
-
v1/manga/recommended
-
v1/ugoira/metadata
後回しで大丈夫
-
illust
-
v2/illust/comment/replies
-
v3/illust/comments
-
-
novel
-
v2/novel/comment/replies
-
v3/novel/comments
-
v2/novel/markers
-
-
user
-
v1/user/bookmark-tags/illust
-
v1/user/bookmark-tags/novel
-
-
search
-
v2/search/autocomplete
-
v1/search/popular-preview/illust
-
-
trending-tags
-
v1/trending-tags/illust
-
v1/trending-tags/novel
-
-
watchlist
-
v1/watchlist/manga
-
v1/watchlist/novel
-
-
v1/spotlight/articles
-
v1/live/list
-
webview/v2/novel
etc.
ver.0.2.0
- 優先的にサポートしたいAPIの実装
-
イラスト/うごイラをダウンロードできるようにする
- イラスト
- うごイラ
-
Illust.type
をString
→Enum
に変更 -
ClientUser.id
をString
→int
に変更 -
Illust
にダウンロードする関数を追加(1枚だけダウンロード, 全部ダウンロード)
freezedのコンストラクタで、デフォルト(nullだった場合)の値にDateTime.now()を設定したい場合
JsonConverterを使う。
fromJsonはnull許容型を受け取り、nullだった場合にDateTime.now()を返すようにする。
toJsonは自動生成した時と同じ処理。
class AcquisitionTimeConverter implements JsonConverter<DateTime, String?> {
const AcquisitionTimeConverter();
DateTime fromJson(String? json) {
if (json != null) {
return DateTime.parse(json);
} else {
return DateTime.now();
}
}
String toJson(DateTime object) {
return object.toIso8601String();
}
}
コンストラクタの中でasync/awaitが使えない
staticメソッドを用意して、それをコンストラクタの代わりにする。
instance_access_to_static_memberがあるので、(無視しない限り)インスタンスからは呼び出せない。
Proxyman for iOSを使う
セットアップ
- ProxymanのVPNを有効化
- 証明書をここからダウンロード(Safariからダウンロードしないと証明書と認識されない)
- 設定アプリの"一般 > VPNとデバイス管理"から証明書をインストール
- 設定アプリの"一般 > 情報 > 証明書信頼設定"からProxymanの証明書を有効化
使い方
- ドメインをピン留めしておくことで、ピンタブからすぐにアクセスできるようになる
AI生成作品
Illus型にillust_ai_type
が追加された。int型で、0=未指定, 1=人が描いたもの、2=AI生成作品。
未指定は対応以前か、7.16より前のアプリから投稿された場合?enum型で扱いたい。
0 = undefined
1 = humanCreated
2 = aiGenerated
isAIGenerated: aiGeneratedの場合のみTrueを返すgetter
AI生成イラストのランキング
- AI生成: day_ai
- R18 AI生成: day_r18_ai
AI生成小説のランキング
- AI生成: week_ai
- R18 AI生成: week_ai_r18
小説にも同じパラメータがある。
アクセストークンが期限切れになった時に自動で更新する
アクセストークンの有効期限は発行から1時間。ブックマークを整理するなど、実行時間が1時間を超える場合もあるので自動で更新させる。
1. BaseClientを拡張し、send関数内でチェック&更新
support for Interceptor · Issue #710 · dart-lang/http · GitHubを参考にBaseClientを拡張する。このClientを使えば、リクエストの送信前にトークンの有効期限がチェック&更新される。
2. RetryCientのon_retry関数内でチェック&更新
httpパッケージの[RetryClient](https://pub.dev/documentation/http/latest/retry/RetryClient-class.html)
を使う方法。トークンの期限切れが原因でリクエストが失敗した場合に、トークンを更新してリトライする。
RetryClient(
http.Client(),
// リトライする条件
when: (response) =>
// アクセストークンの期限が切れている or ステータスコードが503の場合にリトライ
response.statusCode == 400 && !userAccount.isActive() ||
response.statusCode == 503,
// リトライの前に実行される関数
onRetry: (request, response, retryCount) async {
if (!userAccount.isActive()) {
await refreshAccount();
// 更新したアクセストークンをヘッダーに設定
request.headers['Authorization'] = 'Bearer ${userAccount.accessToken}';
}
},
);
3. APIClient内の_get,_post関数内で更新
get, postをAPIClient内の関数から叩くようにして、その中でアクセストークンの期限をチェック&更新する。
4. ヘッダーを取得するgetHeader関数内で更新
ヘッダーを使う時はgetHeaderから返ってきたものを使うようにして、その中でチェック&更新する。
3で実装した後、こちらに移行。
Listからクエリストリングを作る
ブックマークの追加などで、クエリストリングにリストを含める必要がある。
Uri.https()
などでは同名のKeyは上書きされてしまうので、関数を作った。
index付き(tags[0]=text&tags[1]=text
みたいな)も後々必要になるので、それもできるように。
// これから
final tags = ['オリジナル', 'ケーブルニット', '青眼', '黒髪', 'ポケットに手', '女の子', '星空', 'ロングヘアー', '見上げる'];
// これを生成したい
final tagsQuery = 'tags[]=オリジナル&tags[]=ケーブルニット&tags[]=青眼&tags[]=黒髪&tags[]=ポケットに手&tags[]=女の子&tags[]=星空&tags[]=ロングヘアー&tags[]=見上げる'
// クエリストリングを生成する関数
String toQuery(List<String> list, String name, [bool index = false]) {
final buffer = StringBuffer();
for (int i = 0; i < list.length; i++) {
if (i > 0) {
buffer.write('&');
}
buffer.write('$name[');
if (index) {
buffer.write(i.toString());
}
buffer.write(']=${list[i]}');
}
return buffer.toString();
}
Gitのコミットログを整理したい
理由
コミットメッセージを(何故か)英語で書いてたが、英語よわよわだし翻訳するのも面倒なので、過去分も含めて全部日本語にしたい
どうやるの
既にプッシュしている"v0.2.0"は、この記事のやり方でできそう。
リモートブランチを削除すれば、コンフリクトせずプッシュできるらしい。既にmainブランチへ取り込んだ変更はコンフリクト覚悟でやるしかない……?