EC ナビ・PeX の「まいにちニュース」に気持ちを自動で回答するブックマークレットを作った

「EC ナビ」と「PeX」というポイントサイトを、2003年頃から使っている。その中に「まいにちニュース」というコンテンツがある。これは、ニュース記事の末尾に「いいね」ボタンや「Bad」ボタンなどがあり、記事を読んだ気持ちを答えることでポイントがもらえる仕組みだ。

今回はこのボタンを自動的に押下するブックマークレットを作ってみた。

目次

完成形

完成形は以下。以下のブックマークレットを EC ナビや PeX の「まいにちニュース」の記事ページで実行すれば、「いいね」ボタンがあるところまでスクロールして「いいね」ボタンをクリックしてくれる。

javascript:(e=>['angry','sad','cool','like'].map(x=>'#submit-'+x).concat(['bad','sad','glad','good'].map(x=>['.btn_'+x,'.btn_feeling_'+x]).flat()).some(x=>(e=document.querySelector(x))&&(e.scrollIntoView(),e.click(),!0)))();

以下、開発経緯

最初に作ったコード

まずは押下したいボタンの要素を特定する。EC ナビの PC 版は .btn_feeling_XXX、EC ナビのスマホ版は .btn_XXX、PeX は #submit-XXX という名前のボタンを押下すれば良いことが分かった。ただし、EC ナビは「bad」「sad」「glad」「good」の4種類であるのに対して、PeX は「angry」「sad」「cool」「like」と、XXX 部分に入れる「感情」の文言が若干異なっていた。

[
  // EC ナビ PC        // スマホ    // PeX
  '.btn_feeling_bad' , '.btn_bad' , '#submit-angry',
  '.btn_feeling_sad' , '.btn_sad' , '#submit-sad'  ,
  '.btn_feeling_glad', '.btn_glad', '#submit-cool' ,
  '.btn_feeling_good', '.btn_good', '#submit-like'
]

最初に作ったのは以下のようなコード。配列を順に走査し、要素が見つかれば scrollIntoView() でその要素のところまでスクロールし、クリックするという動きにした。break を使って、一度クリックできたらループから抜けるようにした。

javascript:((s, i, e) => {
  for(i = s.length; i--; ) {
    if(e = document.querySelector(s[i])) {
      e.scrollIntoView();
      e.click();
      break;
    }
  }
})(
  [
    '.btn_feeling_bad' , '.btn_bad' , '#submit-angry',
    '.btn_feeling_sad' , '.btn_sad' , '#submit-sad'  ,
    '.btn_feeling_glad', '.btn_glad', '#submit-cool' ,
    '.btn_feeling_good', '.btn_good', '#submit-like'
  ]
);

変数 ie はローカル変数として使うため、ド頭に宣言だけしている。if 文の中で代入しながら存在チェックを行っているところがコード短縮化のミソ。1行にまとめた際は、for 文のブレース {} が除去できる。なお、for ループは文字数短縮のために末尾から順にループするイディオムを利用しているので、配列末尾の方に優先的にクリックさせたい要素を並べておくと良いだろう。

コレでも動作するのだが、なんとなく冗長な気がして、もう少し文字数を減らせないか試してみた。

対象要素の配列を短縮化

まず、.btn_feeling_$submit- といった文言が重複しているので、ココを自動生成できないか考えてみた。

const pexElems = ['angry', 'sad', 'cool', 'like'].map((name) => {
  return '#submit-' + name;
});

const ecNaviElems = ['bad', 'sad', 'glad', 'good'].map((name) => {
  return ['.btn_' + name, '.btn_feeling_' + name];
});

const useFlat        = pexElems.concat(ecNaviElems.flat());
const useConcatApply = Array.prototype.concat.apply(pexElems, ecNaviElems);

このように、共通する文言を map() で付与して配列を生成してみた。EC ナビの方は .btn_XXX (スマホ版) と .btn_feeling_XXX (PC 版) とを同時に生成してみたかったのだが、上の変数 ecNaviElems は二重の配列として生成されている。

二重の配列を展開・フラット化するには、Array.prototype.flat() という関数が策定されている。コレを使って ecNaviElems を平たくし、pexElems と結合すれば良い。

別の方法で、Array.prototype.concat.apply(baseArray, nestedArray) といったイディオムもある。Array.prototype.flat() が動かないブラウザではコチラが使えるだろう。

というワケで、配列の宣言部分は次のようなコードに短縮化できた。

['angry', 'sad', 'cool', 'like']
  .map(x => '#submit-' + x)
  .concat(
    ['bad', 'sad', 'glad', 'good']
      .map(x => ['.btn_' + x, '.btn_feeling_' +x ])
      .flat()
  );

外側の map().concat() しているのが PeX 向けの配列で、内側で map().flat() しているのが EC ナビ向けの配列だ。

forbreak のイディオムを Array.prototype.some() に変更

次に、forbreak で処理していた部分を短くできないか見てみた。

配列を順に操作している時にループを抜ける方法には、Array.prototype.some() も存在することに気付いた。要素が見つかって、クリックができたら return true してやれば、以降の要素は走査されないワケだ。

allElems.some((name) => {
  if(elem = document.querySelector(name)) {
    elem.scrollIntoView();
    elem.click();
    return true;
  }
});

if 文に合致しなかった場合は何も返していないので undefined (Falsy な値) が返ったことになり、ループ処理が続く。

コレをこのまま1行にしても、イマイチ短くならない。しかし、&& 演算子を使った書き方にかえてやると、短くできそうだ。ちなみにこの && 演算子のことは 「論理積」「論理 AND」演算子 と呼ぶ。バイナリ論理演算子の一種だ。

allElems.some( name => (elem = document.querySelector(name)) && (elem.scrollIntoView(), elem.click(), true) );

完成形のおさらい

というワケで、全体を結合するとこのようなコードになる。

javascript:(e =>
  ['angry', 'sad', 'cool', 'like']
    .map(x => '#submit-' + x)
    .concat(
      ['bad', 'sad', 'glad', 'good']
        .map(x => ['.btn_' + x, '.btn_feeling_' + x])
        .flat()
    )
    .some(x =>
      (e = document.querySelector(x)) && (e.scrollIntoView(), e.click(), !0)
    )
)();

コレのスペースを除去して1行にまとめたのが冒頭のコード。

javascript:(e=>['angry','sad','cool','like'].map(x=>'#submit-'+x).concat(['bad','sad','glad','good'].map(x=>['.btn_'+x,'.btn_feeling_'+x]).flat()).some(x=>(e=document.querySelector(x))&&(e.scrollIntoView(),e.click(),!0)))();

その他

ブックマークレットを作るための短縮化には、拙作の @neos21/bookmarkletify という npm パッケージが有効かと思われる。コチラも合わせてドウゾ。

以前作った、ポイントサイトやアンケートサイトで使えるブックマークレットは、別ブログ Corredor の方でいくつか紹介している。コチラもよかったらドウゾ。

以上。