Open11

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.typeStringEnumに変更
  • ClientUser.idStringintに変更
  • 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();
  }
}
むにゃねこむにゃねこ

Proxyman for iOSを使う

Proxyman 公式HP

セットアップ

  1. ProxymanのVPNを有効化
  2. 証明書をここからダウンロード(Safariからダウンロードしないと証明書と認識されない)
  3. 設定アプリの"一般 > VPNとデバイス管理"から証明書をインストール
  4. 設定アプリの"一般 > 情報 > 証明書信頼設定"から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"は、この記事のやり方でできそう。
https://qiita.com/getty104/items/a9b56f30744dfe52a05f
リモートブランチを削除すれば、コンフリクトせずプッシュできるらしい。
https://senooken.jp/post/2021/01/14/4950/

既にmainブランチへ取り込んだ変更はコンフリクト覚悟でやるしかない……?