Chrome拡張"OneTab"から、ブックマークにエクスポートする


経緯、タブをoneTabに貯め過ぎて、整理が不可能に。

気づいたら1000個ぐらい。縦に長過ぎて整理が不可能。折り畳めないしマージもできない。
じゃあ、ブックマークに戻そう。がきっかけ。

oneTabに使えるエクスポート機能は無い

  • まずChromeに取り込むには、特定形式のbookmarks.htmlにする必要ある。
    • Chromeからブックマークを書き出した時のファイル。
    • NETSCAPE-Bookmark-file-1形式?
  • oneTabに簡易の書き出し機能はあるが、これは単純なURL改行のリスト。
    • そもそも日付情報が無い。
    • 日付は重要なので捨てたくない。
  • まあ、便利なエクスポート機能あるとユーザー出て行きやすいから。
    • あえて付けなかったり(想像)、有料にしたりしてるのをよく見る。

表示部分からデータを解析する。

  • 表示部分には日付データがちゃんと残ってる。
  • ターゲットはChrome拡張なので、Bookmarkletは使えない。
  • ctrl+shift+I で開発者ツールのconsoleからやる。
    • 複数行のコードを弄るのは大変なので、ソース→スペニットを使って作る。
  • 試行錯誤して・・・出来た。

この記事投稿してから気づいた大失態

  • oneTabのローカルストレージにJSONでデータが保存してあった!
  • なので表示から解析する必要がなかった。
  • でもまあ、書き出したものは同じだし、面倒くさいからそのままでいいか。

スクリプトの使い方

  • 下のコードをコピーして、ontab開いて、開発者ツールのコンソールに貼り付けて実行。
    • ただそういう攻撃もあるらしいので、軽くソースを読んでからのほうがいいらしい。
  • 出来たhtmlファイルを、Chrome→ブックマークマネージャ→インポート
  • スクリプトの主な機能
    • Chromeにインポートできる形式のhtmlを書き出す。
    • ブックマークにoneTabでの作成日を付与。
    • faviconも書き出す。
/* eslint no-undef: "error" */
//便利関数
var log = console.log
  , qsa = (s, o = document) => [...o.querySelectorAll(s)]
  , qs = (s, o = document) => o.querySelector(s)

function saveTextToFile(fileName, text) {
  const blob = new Blob([text], {type: 'text/plan'})
  const link = document.createElement('a')
  link.href = URL.createObjectURL(blob)
  link.download = fileName
  link.click()
}
//dataURL化
function getBase64(img) {
  var cvs = document.createElement('canvas')
  cvs.width = img.naturalWidth
  cvs.height = img.naturalHeight
  var ctx = cvs.getContext('2d')
  ctx.drawImage(img, 0, 0)
  //枠つけ
  // ctx.lineWidth = cvs.width / 3
  // ctx.strokeStyle = '#0f0c'
  // ctx.strokeRect(0, 0, cvs.width, cvs.height)
  //canvas要素をBase64化する
  return cvs.toDataURL('image/png')
}

function oneTab2html() {
  //表示から解析してdataに入れる。
  var data = qsa('.tabGroup').map((v) => {
    let dateText = qsa('div', v).find(v => v.innerHTML.startsWith('作成日時')).textContent.slice(5)
    let unixtime = new Date(dateText).getTime() / 1000
    let obj = {
      tabs: null,
      title: (qs('.tabGroupTitleText', v).textContent.trim() + '_' || '') + dateText,
      time: unixtime,
    }
    obj.tabs = qsa('.tab', v).map(v => ({
      url: qs('a', v).href,
      title: qs('a', v).textContent,
      favicon: (() => {
        let el = qs('img:nth-child(1)', v)
        if (el && el.src.startsWith('chrome://'))
          return getBase64(el) //''//
        return ''
      })(),
    }))
    return obj
  })

  //dataからhtmlに書き出す
  var folders = ''
  data.forEach(v => {
    folders += `    <DT><H3 ADD_DATE="${v.time}" >${v.title}</H3>\n`
    folders += '    <DL>\n'
    v.tabs.forEach(a =>
      folders += `        <DT><A HREF="${a.url}" ADD_DATE="${v.time}" ICON="${a.favicon}">${a.title}</A>\n`
    )
    folders += '    </DL>\n'
  })

  var html = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
     It will be read and overwritten.
     DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>
${folders}
</DL><p>
`
  //ファイルとして保存
  saveTextToFile('OneTab→bookmarks.html', html)
  return true
}
oneTab2html() && '完了'

インポート後、日付が書き出せてるか、確認する方法

  • 素のChromeでは、ブックマークの作成日を確認する方法は見つからなかった。
  • なので以下の拡張機能を使いました。

自分用の雑記、試行錯誤のおおよそ時系列。

  • chromeから書き出したhtmlファイルを色々試す
    • aadd_date属性にUNIX時間を入れるらしい。
    • その他ブックマークのほうがネストが浅くて分かりやすい。
    • H3をフォルダと認識
      • フォルダにもadd_date時間を付けれる、LAST_MODIFIED
    • ICON="data:image/png;base64,
      • base64のiconデータも渡せる。
    • インポート
      • インポート時はブックマークツールバーに入れられる
      • ブックマークツールバーが空の時以外は、インポートしたブックマークフォルダが作られそこに収まる。
        • そのフォルダが存在する時は、同名のフォルダが作られる。
      • エクスポート時のフォルダ名が同じものは一つにマージされる、ブックマークは両方残る。
  • oneTabの表示から解析
    • googleとかoverflowとか一部のサイトはfaviconのsrcが無く、よくわからない。放置。
    • [{tabs:[{href,title,favicon},],time:UNIX時間,title:フォルダ名},]
    • みたいな形式にまず変換。
  • 出来上がったhtmlをctrl+s保存するために、window.open()を使う。
    • Chromeはブックマークのインポート形式がhtmlだから。
    • about:blankではctrl+sの保存できなかった。なぜぇ。
    • example.com開いて書き換えれば?→セキュリティー的に無理
    • もういいや、onetabの画面を直接書き換えてしまえ。
      • ctrl+sは可能だったが、
      • 保存した瞬間ファイルが削除される怪奇現象。chrome-ext://だからぽい。
    • about:blankにhtml表示させて、そのouterHTMLをコピーしよう
      • document.documentElement.outerHTMLをcopy()して、htmlに貼り付けた。
      • V1.html完成
  • V1.htmlインポートしみてる
    • インポート失敗。まあそうか・・・。
    • インポートしてみるも数個の登録されるだけでおかしい。
    • Chromeが書き出したファイルと見比べると、Chromeはタグが大文字。!?
    • Chrome書き出しファイルを小文字にしてインポートテスト→反応なし。
      • タグは大文字必須?!
    • 大文字にしてからhtmlにparseしてからコピーすると、タグが小文字になってしまう。
      • タグと属性だけ大文字にするの・・・めんどくさいからparseするのやめよう。
  • html→保存、でなく直接書き出すことにした。
    • Chrome書き出しファイルを雛形にして書き出してコピー→htmlファイルに貼り付け。
    • V2.html完成
    • インポートするも、反映されない。
    • href←これも大文字必須だった。疲れた。もう全部大文字にしよう。
    • 修正版を読み込ます→成功・・・日付がおかしい。
      • ブックマークの日付を調べるにはそれっぽい拡張が必要。
    • UNIX時間はミリ秒じゃなく秒だった。
    • インポート成功!!
  • とりあえず機能的に完成。
  • ここまで来たら、Chromeが書き出したファイルで最小化を調べてる
    • pタグを消してみる、問題なし、
    • フォルダ前のdl dt 削除、OK
    • <h3>のフォルダより前は全部不要。文字コードはutf8なら削除できる。
    • <DL><DT>は改行無いと読み込まない→まじかあ。HTMLのこと詳しくないけど。
    • タイトルやH1は必要無い。
    • なんでこんな不親切な仕様なんだろう、超かしこ集団が作ってるブラウザなのに。
    • ADD_DATEはミリ秒じゃないく秒。10桁ぐらい。
    • <!DOCTYPE NETSCAPE-Bookmark-file-1>て検索すると情報が少し出てくる。
  • favicon取り込み(やらなくてもいい)
    • ついでにfaviconに色つけてみようかな。
    • そしたら、リンク開く→アイコン更新で、visited的なことできそう。
    • そもそも更新されるのかな?
    • とりあえず取り込もう
    • canvas作ったにimg貼り付ける、naturalWidthを初めて知る。
    • base64化で、toData()→エラー
    • クロスドメイン問題にひっかかった・・・別オリジンのimg貼り付けがあかんらしい。
      • ダメ元で.setAttribute("crossorigin","anonymous")
      • 画像が壊れマーク。エラーは出なくなった。返り値は空に・・・。
      • DOMでスクリーンショット取れるとか聞いたことある、それはどうか?
    • そもそもクロスドメインで全部弾かれてないんじゃないか?
      • onetabオリジン→chrome-extension://burabura
      • icon1→chrome://burabrua
      • icon2→https://s2.googleusercontent.com
      • ちゃんと調べるとhttp系だけが弾かれてる。じゃあこっち捨てよう。
    • 完成、iconに色枠が付→見ると消えた。
      • この機能要らない。
    • 気づいたこと
      • ブックマークマネージャーだと同じfaviconは共通表示されてた。
      • ツールバーだと共通表示されない。初めて気づいた。
      • いままで後で読む、の区別にこの挙動を利用してた。
  • 別のQiitaのBookmarkletの記事を読んでたら、偶然、ファイル保存の仕方を知る。
  • Qiitaへ投稿
  • onetabのローカルストレージにJSON発見。
    • 個別のタブデータに日付情報は無し。
    • faviconもChromeURLでfavicon返すのに突っ込めばよさそう。
    • favicon返しに使えない例外の判定は作る必要ありそう。

bookmarks.htmlの最小構成を検証した結果

<H3 ADD_DATE="1617199649" LAST_MODIFIED="0">フォルダ1</H3>
    <DL>
        <DT><A HREF="https://news.google.com/" ADD_DATE="1606126600" ICON="data:image/png;base64,burabura=">ニュース</A>
    </DL>

手間取った点

  • Chrome開発者ツールのスペニットで作ってみた。
    • サジェスト効かないし、コーフォドーマットが汚すぎて読みづらい。
  • インポート形式の仕様がもっとゆるいと思い込み、試す→弾かれる、の繰り返しに疲れた。
    • 最初から完コピを目指すべきだった。
  • 軽い気持ちでワンライナーや、アロー関数オブジェクト返しを多様。
    • アイテムが多い上に例外多くて、そこでエラーでとまると、どこでエラーが起きたか分かりにくい。
    • console.assrtを使うべきかな。
  • テンプレートリテラルの中でforEachとかすると、凄く読みづらい。
    • join()ぐらいまでにすべき。

参考にさせてもらったページ