Chrome拡張機能:ダウンロードしたファイルをアップロードする


はじめに

やりたいこと

以下の操作を自動化する。

  1. Flickr等で見つけた自由利用可能な画像ファイルをダウンロードする。
  2. アップロードフォームにダウンロードした画像ファイルを指定してアップロードする。

制限事項

  • たとえChrome拡張機能がダウンロードしたものでも、ローカルのファイルにはアクセスできない。
  • アップロードフォームのファイル選択ダイアログをChrome拡張機能から操作できない。

こうした制限から、メモリ上の画像データから送信データを作成し、アップロードフォームを使わずに送信する必要がある。

手順1:画像ファイルのパスを探す

写真共有サイトは、オリジナル画像を直接表示していない。通信量削減のため縮小したものが表示されているので、まずはリンクをたどってオリジナル画像のURLを取得する。(Flickrの場合は、右下のアイコン「Download this photo」にリンクがある。)

その際、画像ファイルが置かれているドメインを確認し、manifest.jsonのpermissionsに追加する。(Flickrの場合は、「https://c1.staticflickr.com/」など。)ワイルドカードが使えないので、連番が降られている場合はすべての番号を追加する必要がある。

手順2:画像ファイルのダウンロード

XMLHttpRequestのresponseTypeblobを指定することで、画像ファイルをバイナリデータとして取得できる。

background.js
const DownloadBlob = (url) => {
  const p = new Promise(resolve => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';
    xhr.addEventListener('loadend', () => {resolve(xhr.response);});
    xhr.send();
  });

  return p;
}

const DownloadImage = async (url) => {
  const imageFile = await DownloadBlob(url);
}

手順3:送信データを作成する

フォーム
<body>
  <form>
    <input type="file" name="file" value=""/><br/>
    <input type="text" name="title" value="タイトル"/><br/>
    <input type="hidden" name="session_id" value="123456789"/>
    <input type="submit" name="submit" value="送信"/>
  </form>
</body>
background.js
const formData = new FormData();
formData.append('file', imageFile, 'ファイル名');
formData.append('title', 'タイトル');
formData.append('session_id', '123456789');
formData.append('submit', '送信'); //これはいらないかも。

FormDataオブジェクトを作成し、name属性をキーに値を追加していく。

ファイルを追加する場合は、第三引数にファイル名を指定する。ファイル名はダウンロードしたURLから切り出してもよいが、自由につけることもできる。

上記のsession_idのように、非表示になっている値も漏れなく追加する。

手順4:データを送信する

作成したFormDataオブジェクトをフォームのURLに送信する。cookieを必要とする場合は、XMLHttpRequestのwithCredentialstrueを指定して、フォームを受信してから送信する。

background.js
const GetForm = (url) => {
  const p = new Promise(resolve => {
    const xhr = new XMLHttpRequest();
    xhr.open('Get', url, true);
    xhr.responseType = 'text';
    xhr.withCredentials = true;
    xhr.addEventListener('loadend', () => {resolve(xhr.response);});
    xhr.send();
  });

  return p;
}

const PostForm = (url, formData) => {
  const p = new Promise(resolve => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.responseType = 'text';
    xhr.withCredentials = true;
    xhr.addEventListener('loadend', () => {resolve(xhr.response);});
    xhr.send(formData);
  });

  return p;
}

const UploadImage = async (url, formData) => {
  await GetForm (url);
  await PostForm (url, formData);
}

注意

フォームデータを自作するため、アップロードフォームに備わっているバリデーションチェックが行われない。送信前にフォームデータの整合性を確認する必要がある。