🦕

Denoで10年間の過去ツイートをPixelaグラフに取り込む

2021/06/23に公開

@kawarimidollのアカウントはいつの間にか登録から10年以上が経過していました。
(後から登録した他のサイトと違ってTwitterだけKawarimiDollでパスカルケース…)

これを見て、実際自分がどれくらい使っているのか確認したくなりした。
今回はPixelaを使って草を生やして確認したいと思います。
https://pixe.la/ja

作業記録(Zenn Scrap)はこちら

Pixelaとは

GitHubの草のごとく、なんでもピクセルグラフにして記録を取れるツールです。

基本的にAPI経由でしか操作できないところが特徴的で、開発者向けのツールという感じがします。

https://docs.pixe.la/

唐突に宣伝

そういえば「草っぽい文字画像を作るツール」を以前作っていました。

当時はZennのアカウントを持っていなかったのでQiitaに解説があります。
https://qiita.com/kawarimidoll/items/14ce32323715389f1b88

なお、これもDeno 🦕 で動いています。

Pixelaにユーザー登録する

さて、本題に戻ります。

まずPixelaにユーザー登録が必要です。
Create User APIからユーザー作成を行ってください。
https://docs.pixe.la/entry/post-user

規約の同意等に関する項目があるのでコード例は控えます。
ユーザー登録ができると、https://pixe.la/@<username>にユーザーページがつくられます。
https://pixe.la/@kawarimidoll

グラフを作成する

グラフの作成を行います。
Create Graph APIを使います。
https://docs.pixe.la/entry/post-graph

❯  curl -X POST https://pixe.la/v1/users/kawarimidoll/graphs -H 'X-USER-TOKEN:xxxxx' -d '{"id":"tweets","name":"tweets","unit":"tweets","type":"int","color":"sora","timezone":"Asia/Tokyo"}'
{"message":"Success.","isSuccess":true}

Twitterなので青系のグラフにしてみました。

ブラウザからhttps://pixe.la/v1/users/<username>/graphs/<graphID>.htmlにアクセスすると、テーブルを確認できます。

https://pixe.la/v1/users/kawarimidoll/graphs/tweets.html

空っぽのグラフが作成できました。

データを取得する

過去のツイート数はtwilogのステータスページから確認します。 ここ見ればPixela使わなくて良いじゃんとか言わない

https://twilog.org/KawarimiDoll/stats

ページのソースを見てみると、scriptタグにデータが埋め込まれ、画面の表示に使われているようです。

ar_dataにツイート数が、ar_lblに日付が格納されており、日次データはインデックス1となっています。
ということで、以下のコードで取得できます。

fetch_twilog.ts
import { ky } from "./deps.ts";

const username = "kawarimidoll";

const html = await ky(`https://twilog.org/${username}/stats`).text();

const tweets = html.match(/ar_data\[1\]\s*=\s*\[([^\]]+)\];/);
const dates = html.match(/ar_lbl\[1\]\s*=\s*\[([^\]]+)\];/);

if (!tweets || !dates || !tweets[1] || !dates[1]) {
  console.warn("fetch failed");
  Deno.exit(1);
}

const getLength = 365;
const tweetsArray = tweets[1].split(",").slice(-getLength);
const datesArray = dates[1].split(",").slice(-getLength);

console.log(tweetsArray);
console.log(datesArray);

なおkyについては以下の記事で解説しています。
https://zenn.dev/kawarimidoll/articles/13c3f75f6f22d6

ひとまず1年分、365件を取り出してみました。古いものから並んでいるので、slice(-365)で後ろから抽出します。

❯ deno run --allow-net fetch_twilog.ts
[
  "3",  "14", "7",  "4",  "67", "2",  "1",  "0",  "1",  "2",  "1",
  "73", "2",  "2",  "1",  "1",  "2",  "3",  "98", "3",  "1",  "0",
  "1",  "4",  "2",  "49", "0",  "1",  "7",  "2",  "2",  "4",  "82",
  "1",  "0",  "7",  "5",  "2",  "4",  "74", "0",  "2",  "2",  "5",
  "0",  "3",  "78", "7",  "1",  "0",  "0",  "1",  "2",  "85", "3",
  "3",  "3",  "1",  "1",  "5",  "80", "2",  "4",  "15", "10", "4",
  "2",  "38", "2",  "1",  "6",  "12", "6",  "5",  "81", "0",  "0",
  "4",  "1",  "2",  "2",  "89", "3",  "2",  "2",  "4",  "6",  "5",
  "88", "5",  "1",  "4",  "1",  "2",  "0",  "56", "3",  "2",  "6",
  "8",
  ... 265 more items
]
[
  "'200624'", "'200625'", "'200626'", "'200627'", "'200628'",
  "'200629'", "'200630'", "'200701'", "'200702'", "'200703'",
  "'200704'", "'200705'", "'200706'", "'200707'", "'200708'",
  "'200709'", "'200710'", "'200711'", "'200712'", "'200713'",
  "'200714'", "'200715'", "'200716'", "'200717'", "'200718'",
  "'200719'", "'200720'", "'200721'", "'200722'", "'200723'",
  "'200724'", "'200725'", "'200726'", "'200727'", "'200728'",
  "'200729'", "'200730'", "'200731'", "'200801'", "'200802'",
  "'200803'", "'200804'", "'200805'", "'200806'", "'200807'",
  "'200808'", "'200809'", "'200810'", "'200811'", "'200812'",
  "'200813'", "'200814'", "'200815'", "'200816'", "'200817'",
  "'200818'", "'200819'", "'200820'", "'200821'", "'200822'",
  "'200823'", "'200824'", "'200825'", "'200826'", "'200827'",
  "'200828'", "'200829'", "'200830'", "'200831'", "'200901'",
  "'200902'", "'200903'", "'200904'", "'200905'", "'200906'",
  "'200907'", "'200908'", "'200909'", "'200910'", "'200911'",
  "'200912'", "'200913'", "'200914'", "'200915'", "'200916'",
  "'200917'", "'200918'", "'200919'", "'200920'", "'200921'",
  "'200922'", "'200923'", "'200924'", "'200925'", "'200926'",
  "'200927'", "'200928'", "'200929'", "'200930'", "'201001'",
  ... 265 more items
]

良さげです。

日付を整形する

草を生やすには、PixelaのUpdate Pixel APIを使います。
https://docs.pixe.la/entry/put-pixel

ここで、twilogから取得した日付は'yyMMdd'という形式になっていますが、APIのエンドポイントに合わせるため、/v1/users/<username>/graphs/<graphID>/<yyyyMMdd>の形式に変更する必要があります。
日付の整形といっても、世紀をまたぐことは想定されないので、頭に20をつければOKです。

fetch_twilog.ts
 import { ky } from "./deps.ts";

- const username = "kawarimidoll";
+ const twitterUsername = "kawarimidoll";
+ const pixelaUsername = "kawarimidoll";
+ const pixelaGraphId = "tweets";

- const html = await ky(`https://twilog.org/${username}/stats`).text();
+ const html = await ky(`https://twilog.org/${twitterUsername}/stats`).text();

 const tweets = html.match(/ar_data\[1\]\s*=\s*\[([^\]]+)\];/);
 const dates = html.match(/ar_lbl\[1\]\s*=\s*\[([^\]]+)\];/);

 if (!tweets || !dates || !tweets[1] || !dates[1]) {
   console.warn("fetch failed");
   Deno.exit(1);
 }

 const getLength = 365;
 const tweetsArray = tweets[1].split(",").slice(-getLength);
 const datesArray = dates[1].split(",").slice(-getLength);

- console.log(tweetsArray);
- console.log(datesArray);
+ for (let i = 0; i < tweetsArray.length; i++) {
+   const url =
+     `https://pixe.la/v1/users/${pixelaUsername}/graphs/${pixelaGraphId}/20${
+       datesArray[i].replaceAll("'", "")
+     }`;
+   const quantity = tweetsArray[i];
+   console.log(url, quantity);
+ }
❯ deno run --allow-net fetch_twilog.ts
https://pixe.la/v1/users/kawarimidoll/graphs/tweets/20200624 3
https://pixe.la/v1/users/kawarimidoll/graphs/tweets/20200625 14
https://pixe.la/v1/users/kawarimidoll/graphs/tweets/20200626 7
https://pixe.la/v1/users/kawarimidoll/graphs/tweets/20200627 4
# (略)

OKですね。
後はこれに順次リクエストをかければ草が生えていきます。

草を生やす

ではリクエストを実行していきます。
※以下は全件について実行するコードですが、自分が最初に試したときはテストで数件から行いました。

一度にリクエストをかけると攻撃になりかねないと思い、ループごとに2秒のインターバルを置きました。

fetch_twilog.ts
 import { ky } from "./deps.ts";
+ import { PIXELA_TOKEN } from "./env.ts";

 const twitterUsername = "kawarimidoll";
 const pixelaUsername = "kawarimidoll";
 const pixelaGraphId = "tweets";

 const html = await ky(`https://twilog.org/${twitterUsername}/stats`).text();

 const tweets = html.match(/ar_data\[1\]\s*=\s*\[([^\]]+)\];/);
 const dates = html.match(/ar_lbl\[1\]\s*=\s*\[([^\]]+)\];/);

 if (!tweets || !dates || !tweets[1] || !dates[1]) {
   console.warn("fetch failed");
   Deno.exit(1);
 }

- const getLength = 365;
- const tweetsArray = tweets[1].split(",").slice(-getLength);
- const datesArray = dates[1].split(",").slice(-getLength);
+ const tweetsArray = tweets[1].split(",");
+ const datesArray = dates[1].split(",");

 for (let i = 0; i < tweetsArray.length; i++) {
   const url =
     `https://pixe.la/v1/users/${pixelaUsername}/graphs/${pixelaGraphId}/20${
       datesArray[i].replaceAll("'", "")
     }`;
-  const quantity = tweetsArray[i];
-  console.log(url, quantity);
+  const json = { quantity: tweetsArray[i] };
+  console.log(url, json);

+  console.log(
+    await ky.put(url, { headers: { "X-USER-TOKEN": PIXELA_TOKEN }, json })
+      .json(),
+  );
+  // wait 2 sec...
+  await new Promise((resolve) => setTimeout(resolve, 2000));
 }

なお、トークンをenv.tsから読み込んでいますが、ここの作りに関しては以下の記事にまとめています。
https://zenn.dev/kawarimidoll/articles/1c48c097020cbc

実行します。

❯ deno run --allow-net --allow-read --allow-env fetch_twilog.ts
https://pixe.la/v1/users/kawarimidoll/graphs/tweets/20210621 { quantity: "16" }
{ message: "Success.", isSuccess: true }
https://pixe.la/v1/users/kawarimidoll/graphs/tweets/20210622 { quantity: "23" }
{ message: "Success.", isSuccess: true }
(略)

だーーーーー……

10年プラス半年で3800件程度のデータがありました。1件ごとに2秒のインターバルが入るので…それなりに時間がかかります。

全期間適用できました。
Freeユーザーは1年以上前の草は見られないのですが、データとしては存在しています。

非常に個人的なグラフ表示結果の分析

しばらくニチアサ実況アカウントだったので日曜だけツイートが多く、他の日はほとんどつぶやいていませんでした。
キラメイジャーが完結したのを機にテレビを手放したので、3月くらいから一気にツイートが減りました。
5月末にvim-jpのslackに参入したことをきっかけにツイッターにinする時間も増え、一気に色が濃くなっています。

これ以降も同じ処理を行えばグラフを更新できますが、もちろん毎回10年のデータを取得する必要はありません。
たとえばデイリーバッチで毎日実行するなら、最新1日ぶんずつ実行すれば良いことになります。

おわりに

Twilogからデータを取得してPixelaへ投げるという処理を実装してみました。
DenoとKyのおかげで必要最低限の記述で済んでいます。

GitHubの草もそうですが、活動が可視化されるとやる気が出ますね。
他のものもPixelaに繋げられないか、いろいろ考えようと思います。

参考

https://sue445.hatenablog.com/entry/2018/10/21/112107

Discussion