1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DownloadClassifierというツールを作りました

DownloadClassifier

ダウンロードしたファイルを自動的にフォルダに分けるツールです
もともとDownloadRouterというツールがあったのですが、ManifestV2だったため消滅
後継がそのうち出るだろ…って思ってたのに出ないので自分で作りました

Github Copilotが話題

最近はAIプログラムが流行っていて、ちょっと使ってみようかな…という感じで始めました

しかし、ChromeExtensionなんて使ったことは稀
なので、Copilotに「Chromeの拡張の雛形を作って」とお願いしたら簡単に作ってもらえました

あとは、「option画面作って」「いい感じにレイアウトにして」みたいな指示を出すだけ
え、これで本当に出来るの!?

特に困った部分としては、Downloadしたファイルを別のフォルダに移動するところです
これに関しては、chrome.downloads.onDeterminingFilename.addListenerという物を使えばいいのですが
情報としてこれを見つけるのに30分、使い方を調べるのに30分…
そんなことを物の1分もせずにサンプルコード書いてくれるCopilot君はやべぇ…

問題点

ファイル名振り分けはこんな感じでした

function getRules(callback) {
  chrome.storage.sync.get(['rules'], (result) => {
    const rules = (result.rules || []).map(ruleObj => new DownloadRule(ruleObj));
    callback(rules);
  });
}

chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
  getRules((rules) => {
    const matched = rules.find(rule => rule.match(item));
    if (matched && matched.folder) {
      const filename = item.filename.split(/[\\/]/).pop();
      suggest({ filename: `${matched.folder}/${filename}` });
      return;
    } else {
      suggest();
      return;
    }
  });
});

chrome.storage.sync.getで設定ファイルを読み込み、
ルールに従い、suggestを呼び出してフォルダを変えます
変更なしなら、suggestを引数なし呼び出す必要があります

しかし、これではsuggestを2回呼び出されました、というエラーが出ます

そもそも非同期関数を使う意味がある?
※chrome.storage.sync.getのsyncを、非同期を意味するものと勘違いしています

ここって非同期使ってるのがまずいのでは?と指示を出すと…
「はい、onDeterminingFilenameで非同期関数を呼び出すのは推奨されません」
おいいいーー、やっぱりじゃねーか!
知ってるのなら最初っからそうしろよ!!

そうして書き直されたのがこれ

let cachedRules = [];

function updateRulesCache() {
  chrome.storage.sync.get(['rules'], (result) => {
    cachedRules = (result.rules || []).map(ruleObj => new DownloadRule(ruleObj));
  });
}

// 初回ロード時にキャッシュ
updateRulesCache();

// ルールが変更されたらキャッシュを更新
chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'sync' && changes.rules) {
    updateRulesCache();
  }
});

// chrome.downloads.onDeterminingFilename.addListenerは省略

ここで、chrome.storage.syncの勘違いに気が付きます
chrome.storageにはローカルとリモートの2種類があります
ローカルはPCに保存されるもの、リモートはGoogleに保存されるもので、
リモートに保存した場合、別のPCでも同じ設定が読み込めます

syncが意味するのはこのリモート側に保存する、という意味です
非同期関数だからsyncがついているわけではありません

今回、ダウンロードフォルダを振り分ける場合は
ローカルのダウンロードフォルダ構成を合わせる必要があります
なので、リモートに保存する意味はないんですね

ローカルに保存するよう指示したら、キャッシュを使わない方法に戻されました
「onDeterminingFilenameで非同期使って大丈夫?」って聞くと
「ローカルの場合、瞬時に読み込むので大丈夫です」とのこと

しかし、実際には大丈夫ではないんですね
結局、上記ソースのsyncをlocalに置き換えました

この話は後半に続きます

gemini 登場

gemini CLIが爆誕、話題はgemini一色に!
調べてたら、gemini Code Assistというプラグインがあり…
え、これいいじゃん!って感じでcopilotの二刀流になりました

gemini君はめっちゃ優秀で、アイコンをクリックしたとき
過去のダウンロードのファイル名だけ出してたのですが
リファクタリングをお願いしたら…

ファイル名だけでなく、アイコンを付けて、
ダウンロードサイズ、日時、サイト名も出すように変更
デザインもただの羅列からcssを使ったモダンなデザインに!

ソースファイルも今風の構造にごっそり変えられました

優秀なのはいいんだけど、やや我が強いというか…
既存のソース保守に使うと大変なことになりそうです

onDeterminingFilename問題再び

onDeterminingFilenameとchromeの保持しているダウンロード済みの情報
実は中身が違います

つまり、ダウンロードした後に正確な情報に置き換わるのかな、という感じです

しかし、onDeterminingFilenameでフォルダ分けを行う関係から
この時点での情報でルールを作成して振り分けたほうがいいわけです

なので、ローカルストレージにこの情報を残すことにしました
しかし、ローカルストレージにデータを残すためには非同期関数が必要です

色々試した結果、suggestを呼び出した後非同期関数を呼ぶ分には問題なさそうです
しかし、gemini君はこれを良しとせずsuggestの前に持ってきます
お作法的にはそうなんでしょうが…って問い詰めたら…

「非同期関数を呼ぶ場合、return trueとすればうまくいきます」
なんですと???

実際のソースがこれ

chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
  // 非同期で suggest を呼び出すため、リスナーは true を返す必要がある。
  // 即時実行非同期関数 (async IIFE) を使って処理を行う。
  (async () => {
    try {
      const { history } = await chrome.storage.local.get({ history: [] });
      history.unshift(item); // 配列の先頭に新しいアイテムを追加
      await chrome.storage.local.set({ history });
    } catch (e) {
      console.error('Error updating history in onDeterminingFilename:', e);
    }

    // ルールに基づいてファイルパスを決定
    const matched = cachedRules.find(rule => rule.match(item));
    if (matched && matched.folder) {
      const filename = item.filename.split(/[\\/]/).pop();
      const newfilepath = `${matched.folder}/${filename}`;
      suggest({ filename: newfilepath });
    } else {
      suggest(); // ルールにマッチしない場合はデフォルトの動作
    }
  })();

  return true; // suggest() を非同期で呼び出すことを示す
});

gemini君もしっかりとコメントを残してくれています!

AIアシスタント比較

Github Copilot

コードアシスト機能が優秀です
ソースを書いているとき、次に書きたいコードをポンポン出してくれます
キータイプ数が半分以下になるのでメチャクチャ便利

また、チャットでの相談もレスポンスが早く気持ちいいです
反面、ソースに反映するとき若干時間がかかります

後述するgeminiみたいなバグもないので、使いやすいです
ただ、無料枠はだいたい10日ほどで使い切る印象です

gemini code assist

前述した通り、超優秀ながら暴走しがちな新人です
モダンと称される書き方を問答無用で使う傾向にあります
Copilotが今のソースに合わせた提案を行うのとは雲泥の差

しかし、その分、出来上がるものが数段良くなります

ただ問題が2点あります

長文作業をするとトークンが足りなくなります
長文を書き出すと答えを出していたら途中でいきなりエラー表示になります
エラー表示だと、今まで書いていた文章も読めなくなるので、
小分けにする必要があるのですが、
geminiの特性上、一度にいろんなことをやろうとするので小分け指示も大変です

gemini CLIを使うと、もっと長文な作業をしてくれるのでこちらで作業するといいかもです

もう一点、エスケープシーケンス処理が変です
正規表現を書いているといきなりソースの途中で編集が途切れます
どうやらgeminiは書き出すソースの中に特定の文字が入ると
そこを終端とするようです
おいおい、こいつSQLインジェクションみたいなこと出来るんじゃねーのか??

振り返って

1からChrome拡張の勉強を始めて2週間ほどで完成と
普通に作ったらありえない速度で完成しています

聞けばどんどん答えてくれるという家庭教師的側面もあり
学習する気になればどこまででも学習できます

AIを使わないでいると時代に乗り遅れる感覚はあります
まじでそのぐらい強力

AIが人間になりかわる…みたいな話はよくわかりません
どちらかというと、これでいろんなものが作っていける!という
ワクワク感に溢れています

乗るしかない、このビッグウェーブに!!

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?