🏙️

Twitter 上のイラストを快適に閲覧するための Web アプリを公開しました

2023/01/22に公開

Twitter 上の画像を保存し、一覧表示やフィルタリングを通じて快適に閲覧するための Web アプリケーションを公開しました。

アプリケーションのスクリーンショット
スクリーンショット(著作権の都合でモザイクを掛けています)

https://github.com/inaniwaudon/twitter-illustration

動機と目的

Twitter のタイムラインには神絵師さんによるイラストの数々や、世界の絶景を写した風光明媚な写真が昼夜を分かたず流れてきます。しかしながら、如何せんフロー型の SNS であるため一覧性に乏しく、後から見返しにくい、検索機能が弱いといった欠点を持つのが難点です。
めちゃくちゃ好みの絵を見つけたのにインターネットの彼方に見失ってしまった」といった経験、ある……

また、夏休みや大晦日が近づくとコミックマーケット等の同人誌即売会に参加するサークルのおしながきツイート[1]が大量に流れてきますが、これを追うのも一苦労です。

こうした背景を踏まえて[2]、以下の特徴を持つアプリケーションを実装しました。

  • 好みのツイートを保存し、ツイートに掲載された画像を一覧表示できる[3]
  • タグ付けを快適に行うための UI を有し、作品やキャラクター、ラベル等に応じてツイートを分類できる
  • 付与したタグを元に検索(フィルタリング)を行える
  • 保存したツイートはローカルに蓄積されるため、快適かつオフライン環境でも閲覧可能

機能・つかいかた

環境構築に際して、Node.js と SQLite3 の導入および Twitter API の取得が必要です。詳しくは README をご参照ください。

ツイートの追加

本アプリケーションにツイートを追加するには、以下のいずれかの手法を取ります。

  • アプリケーション上で URL を入力する
  • Chrome, Firefox 用拡張機能を導入する

Google Chrome または Firefox に拡張機能を導入すると、Twitter Web App(https://twitter.com)上からツイートを直接追加することができます。画像を含むツイートのページを開くと、右上にある既存の三点リーダの隣に、新たに + ボタンが表示されます。これをクリックすることで、アプリケーションにツイートが追加されます。

拡張機能によって + アイコンが追加されたツイートのページ
拡張機能を導入した状態でのツイートページ

なお当初は Twitter API v2 を利用してツイート情報の取得を行っていましたが、API の無料アクセスの廃止を受けて、拡張機能側でツイートを取得する機能を実装しました。(2023 年 2 月 4 日追記)

https://github.com/inaniwaudon/twitter-illustration/pull/14

ツイートの表示・タグ付け・絞り込み

アプリケーションを起動して http://localhost:3030 を開くと、以下の画面が出現しまます。中央には追加したツイート画像の一覧が、右側には選択したツイートが表示されます。

動作光景

ツイートにはタグを付与して効率的に管理することが可能です(左側サイドパネル)。

ユーザは事前に JSON ファイルを通じてタグを定義しておき、GUI 上で複数のツイートに対して一括でタグ付けを行うことができます。その後は付与したタグやキーワードを用いて自由に絞り込みを行うことができるため、例えば AND 検索で絞り込むと、いわゆる推しカプ[4]の絡みを検索したり、「土曜日」「東地区」等の条件でフィルタリングしたりすることが可能となります。
これでイラストや写真の数々を、ブックマーク欄や過去のリツイートから遡る必要はもうありません……!

色別タグ付け

画像の特定の箇所を Ctrl キーを押しながらクリックすることで、マウスカーソルのある箇所の画素色を取得し、その色に関連付いたキャラクターのタグ候補を提示します。本機能を用いることで、タグ付けをより効率的に行えます。(2023 年 2 月 1 日追記)

画像の特定の箇所をクリックし、画素色に関連付けられたキャラクターをタグ付けする様子

実装

フロントエンド、バックエンド、ブラウザ拡張機能 の 3 つから構成されます。実装期間は 3 日程度です。

フロントエンド

React による SPA (Single Page Application) として実装されています。フィルタリング等はフロント側で処理しています[5]

画面構成としては 3 カラムレイアウトで、左側にナビゲーション(タグ等)、中央に画像の一覧表示、右側にツイートが表示されます。ツイート選択のインタラクションは Google フォト 等を参考に実装しました。
レスポンシブデザインには非対応ですので、Future works として考えていきたいと思います。

バックエンド

Express + Sequelize を用いて実装され、node.js 上で作動します。
バックエンドは API として以下のエンドポイントを有します。ローカルでの動作を想定しているため、認証等を備えない最小限の構成です。

HTTP メソッド エンドポイント 機能
GET /work 作品・キャラクター一覧の取得
GET /common-tag 作品間に共通するタグ(以下、共通タグ)の取得
GET, POST /tweet 追加したツイートの取得、ツイートの追加
GET, POST, DELETE /tweet-tag ツイートとタグの関連付けを取得・追加・削除
GET /image 画像を取得

データの取得と管理

リクエストに応じて Twitter API v2 を利用してツイート情報を取得し、キャッシュとして DB に蓄積します。昨今の何かと話題[6]の API ですね。
API を通じた取得方法は廃止されました。現在は Chrome 拡張を通じた追加をサポートしています。(2023/2/7 追記)

データの管理には、以下のテーブルを持つデータベースを SQLite を用いて構築しています。マイグレーションは Sequelize が自動で行ってくれます。
Images テーブルはツイートに含まれる画像を管理するテーブルですが、バイナリデータは含まず、画像のサイズのみを管理します[7]

CREATE TABLE Users(
  id VARCHAR(255) NOT NULL PRIMARY KEY,
  screenName VARCHAR(255) NOT NULL,
  name VARCHAR(255) NOT NULL,
);
CREATE TABLE Tweets(
  id VARCHAR(255) NOT NULL PRIMARY KEY,
  body VARCHAR(255) NOT NULL,
  userId VARCHAR(255) NOT NULL REFERENCES Users (id),
  tweetCreatedAt DATETIME NOT NULL
);
CREATE TABLE Images(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  tweetId VARCHAR(255) NOT NULL REFERENCES Tweets (id) ON DELETE CASCADE,
  index INTEGER NOT NULL,
  width INTEGER NOT NULL,
  height INTEGER NOT NULL,
  UNIQUE (tweetId, index)
);
CREATE TABLE TweetTags(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  tweetId VARCHAR(255) NOT NULL REFERENCES Tweets (id) ON DELETE CASCADE,
  tag VARCHAR(255) NOT NULL,
  UNIQUE (tweetId, tag)
);

作品・キャラクター情報および共通タグはテキストベースで編集したい気持ちがあったため、JSON 形式での管理としています。

/backend/data/works.json
[
  {
    "title": "ぼっち・ざ・ろっく!",
    "alias": ["ぼざろ"],
    "characters": ["後藤ひとり", "喜多郁代", "伊知地虹夏", "山田リョウ", "伊知地星歌"]
  },
  {
    "title": "やはり俺の青春ラブコメはまちがっている。",
    "alias": "俺ガイル",
    "characters": ["雪ノ下雪乃", "由比ヶ浜由比", "一色いろは", "平塚静"]
  },
  { "title": "C101", "characters": ["金曜日", "土曜日"] }
]

ブラウザ拡張機能

Manifest v3 に準拠する形で実装しました。フロントエンドと同様に TypeScript で開発し、webpack を用いてトランスパイルしています。

manifest.json
{
  "manifest_version": 3,
  ...
  "content_scripts": [
    {
      "matches": ["https://twitter.com/*"],
      "css": ["style.css"],
      "js": ["content-script.js"]
    }
  ],
  "background": {
    "service_worker": "background.js"
  },
  "host_permissions": ["http://localhost:3030/*"]
}

Content Script(content-script.ts)では MutationObserver を利用して DOM を監視します。URLに /status を含む、すなわちツイートの表示時に限って、本来ある三点リーダの隣に独自の + アイコンを追加します。このアイコンが押された際に localhost:3030/tweet に POST メソッドでリクエストを飛ばすのですが、Content Script には CORS の制限が掛かるため、一度 Background Page(background.ts)にメッセージを送り、Background Page から fetch を行います。

ページロード時には、追加済みのツイート一覧を /tweet から取得することで、既に追加済のツイートに ✓ マークを表示しています。

むすびにかえて

今後の展望として、以下に掲げる機能の実装を予定しています。

  • レスポンシブデザイン
  • ページング
    IntersectionObserver を用いた実装で高速化されました。(2023 年 2 月 14 日追記)
  • タイムラインやリストのブラウズ
  • Twitter 以外のサービスへの対応

なお、日本の法律では著作物の私的複製を自由に行えるため、本アプリケーションをローカルで使用するにあたって特に許諾等は必要ありません。ただし、収集したデータをインターネット上にアップロードする行為は公衆送信に該当し、著作権法違反に当たるため注意が必要です[8]

脚注
  1. 例:https://twitter.com/HitenKei/status/1567087077740933127 ↩︎

  2. 直接的な動機としてはぼざろ二次創作の流速が速すぎて追いきれないことに端を発する ↩︎

  3. 感覚としては Pinterest に近い ↩︎

  4. 自分が推している(応援している)カップル ↩︎

  5. 現状では特に不便を感じていませんが、今後処理の重さを感じるようになれば、バックエンド側で抽出処理を実装したいと思います ↩︎

  6. https://www.asahi.com/articles/ASR1N4QQPR1NULFA00K.html ↩︎

  7. 画像サイズのデータはフロントエンドでのレイアウトの決定に利用しています。画像のロードを待たずにレイアウトが決定できるため便利 ↩︎

  8. こうした事情もあり、サービスとしてのリリース予定はありません ↩︎

Discussion