return 1;

Webエンジニア、RailsとJSをメインに仕事しています。チーム開発が得意で、0-1や1-100のどちらも行う

「やく見てる」エクステンション作った

クソアプリアドベントカレンダー 13日目の記事です

こんにちは、ライオンです

皆さん、やくみつるはご存知ですか?

ええ、いろいろと炎上したりするコメンテーターですね

自分のリアルを知っている人はご存知だと思うんですが、よく似ているって言われるんですよね

10人中9人ぐらいに似ているって言われるんですよね

そんなに似ているのかなーって2年前ぐらいに実家に帰ったら兄弟に似ているって爆笑されたんですよね

ショックです

そんなわけでchrome extensionを作ることにしました

開発の話に興味ない人用のリンク googleまで戻る









開発の話

最初、imgタグのsrc変えれば余裕でしょって考えてたんですよね

でやってみたら、こんな感じになったわけですね

f:id:lion_man44:20171202195946p:plain

すごいですよね、皆やくみつるですよ

ざまぁみろ

で、ひとしきりゲラゲラ笑い終わって、いやーぁもう仕事終わっちゃったなー

クソアプリすぎるどころか面白さすらないなーとか考えながらおっぱいの画像を探してたんですよね(精神衛生上の健康のためにお医者さんから処方されている)

で、そしたら途中から気づいたんですよ

f:id:lion_man44:20171202232348g:plain

画像が変わってない

でもすぐに気づくんですよね、lazy loadだって

普通の画像ならsrc属性だけで済むけど、data-*属性を使っているならビンゴです

方法までは難読化されたソースコードを読まないといけないので読んでいませんが、クソアプリにそんな時間はかけていられません

で、こうなったらreplaceChildでimgタグを塗り替えてやるしかないですね

で、書き換えて見て見ました

f:id:lion_man44:20171202235047g:plain

クソが

よくよく考えたらインフィニティスクロールに対して、非同期処理してないなんてありえないですよね

こうなったら何が何でも書き換えてやりたくなりますよね

何とか奪えないかと考え始めます

まぁそうするとsetTimeoutかsetIntervalするしかないかなって考えが出て来ますね

非同期でDOMを追加される以上、そこに存在してないのでそれが追加されるまでをwatchするしかなくなりますね(悲しい

(() => {
  'use strict';

  const replaceMemo = {
    src: 'http://www.xn--l8j6cuc0dv605bd1f.jp/yuumeijin/photo/yakumituru.jpg',
    prepare: [],
  };

  const replaceImage = () => {
    const $images = document.querySelectorAll('img');
    replaceMemo.prepare.forEach($el => {
      const $img = document.createElement('img');
      $img.src = replaceMemo.src;
      $el.parentNode.replaceChild($img, $el);
    });
  };

  const onWatch = () => {
    const $images = document.querySelectorAll('img');
    replaceMemo.prepare = Array.from($images).filter($el => $el.src !== replaceMemo.src);
    replaceImage();
  };

  const main = () => {
    setInterval(onWatch, 1000);
  };

  main();
})();

こんな感じのコードになりました

ここまで行くと瞬間的な表示はあるものの確実に変えられますね

しかしまだこれだと直接指定されているstyleなどが反映されていないですよね

cssTextを使ってそのまま持って来ます

f:id:lion_man44:20171203004746p:plain

こんな感じだったのが...














f:id:lion_man44:20171203153804p:plain

こうなります(画像は大槻ひびきさんです)

はーかわいいですね

で、上の画像見た人なら気づいていると思うんですが、左上の画像がとてつもなく邪魔ですよね?

これ開発中にも何度となく邪魔をされて本当に精神を壊しそうになりました

(googleからおっぱい検索をして画像タブをクリックするわけですが、押せないわけですね!)

で、よくよく調べてみると直接widthとheightを指定しているパターンもあるんですよね

$img.height = $el.height; みたいなコードを追加します

f:id:lion_man44:20171203011442p:plain

綺麗に収まりましたね

皆さんの言いたいことはわかります

「大槻ひびきさん見せろよ!!」ですよね?

ですが、もともとアス比の合わないCSSに対して、決まった画像をあてがうので大槻ひびきさんがひどいことになってしまいます

酷い状態になった女優を見たくないですよね?(血涙






さて、この調子で直していって大体変わるわけですね

これは好きなライターさんの小野ほりでいさんの記事です

f:id:lion_man44:20171203012859p:plain

全部やくみつるでカオスですよね

カオス、カオス以外のことばが思いつかない

文字の色がなかったら誰が喋っているかすらわかりません

つまり小野ほりでいさんはこういうことをされることを分かっていて、それでも読みやすい記事を作ってくださっているわけですね!

で、何気なく下までスクロールしていって「あー大体変わっているなー」と思ったら、また変わってないところがあるわけですよ

f:id:lion_man44:20171203013355p:plain

クソがぁ...

何で変わらないんだろうと思ったらcssの中で呼ばれているURLですよね

backgroundで指定するパターンとbackground-imageで指定するパターンの2つですね

直接書き換えても書き換わらずに、 '' を代入するとなぜか空にはなるという現象に見舞われ、今回もimgタグ同様replaceChildを使う強行手段に出ました

  const replaceImage = (images, aTags, divs) => {
    images.forEach($el => {
      const $img = document.createElement('img');
      $img.className = $el.className;
      $img.style.cssText = $el.style.cssText;
      $img.height = $el.height;
      $img.width = $el.width;
      $img.src = replaceMemo.src;
      $el.parentNode.replaceChild($img, $el);
    });
    aTags.bk.forEach($a => {
      const $newA = document.createElement('a');
      $a.style.background = '';
      $newA.style.cssText = $a.style.cssText;
      $newA.style.background = `url("${replaceMemo.src}")`;
      $a.parentNode.replaceChild($newA, $a);
    });
    aTags.bki.forEach($a => {
      const $newA = document.createElement('a');
      $a.style.backgroundImage = '';
      $newA.style.cssText = $a.style.cssText;
      $newA.style.backgroundImage = `url("${replaceMemo.src}")`;
      $a.parentNode.replaceChild($newA, $a);
    });
    divs.bk.forEach($div => {
      const $newDiv = document.createElement('div');
      $div.style.background = '';
      $newDiv.style.cssText = $div.style.cssText;
      $newDiv.style.background = `url("${replaceMemo.src}")`;
      $div.parentNode.replaceChild($newDiv, $div);
    });
    divs.bki.forEach($div => {
      const $newDiv = document.createElement('div');
      $div.style.backgroundImage = '';
      $newDiv.style.cssText = $div.style.cssText;
      $newDiv.style.backgroundImage = `url("${replaceMemo.src}")`;
      $div.parentNode.replaceChild($newDiv, $div);
    });
  };

  const includeBKUrl = ($el) => {
    return $el.style.background.includes('url');
  };
  const includeBKIUrl = ($el) => {
    return $el.style.backgroundImage.includes('url');
  };

  const onWatch = () => {
    const $images = document.querySelectorAll('img');
    const $aTags = document.querySelectorAll('a');
    const $divs = document.querySelectorAll('div');
    const images = Array.from($images).filter($el => $el.src !== replaceMemo.src);
    const backgroundForATags = Array.from($aTags).filter($a => includeBKUrl($a));
    const backgroundImageForATags = Array.from($aTags).filter($a => includeBKIUrl($a));
    const backgroundForDivs = Array.from($divs).filter($div => includeBKUrl($div));
    const backgroundImageForDivs = Array.from($divs).filter($div => includeBKIUrl($div));
    replaceImage(images, { bk: backgroundForATags, bki: backgroundImageForATags }, { bk: backgroundForDivs, bki: backgroundImageForDivs });
  };

  const main = () => {
    setInterval(onWatch, 1000);
  };

  main();

汚い匂いを放ち始めてワクワクしてきましたね!

ですがそのスペルマコードによって、ようやく姿を変えるわけです

f:id:lion_man44:20171203025719p:plain

Amazing!


しかしここまでやってもまだまだ残っている画像やurl cssが残っているわけですね

必殺の技としては onLoad で直す、というのもあるわけですが、効かなかったです

iframeの中にあるimgタグがconsole上で document.querySelectorAll('img') を出力すると取れるのですが、cotent_script内で動いている document.querySelectorAll('img') からは取れず、ここら辺はセキュリティの問題なんですかね?

なので不十分に残ります

次にやっぱり、同じ画像が出てくるのはあんまり楽しくないですよね

で、何か画像検索できるようなやつがいいなーと思ってやっぱりgoogle様のお力を借りたいですよね

Googleの画像検索APIを使って画像を大量に収集する

を見ていたのですが、「やってられるか!」となりましたね

それはもうすごい勢いで

で、考えた結果webスクレイピングすりゃいいんじゃねってなりますよね

クッソ悩みました

もはや禿げていく人の焦燥感並に悩みました

backgroundで実行するEvent Pageで設定した非同期実行処理がContent Scripts内でどうやってタイミングよく受け取れるのか分からず本当に悩みまくりました

そしたら蛇足に書いてある部分があったことによって助かりました

qiita.com

涙しそうになりますよね、こういう時

嬉しかったのでお昼ご飯はキムチ鍋にしました

background.jsにはこんな感じで用意

const parser = new DOMParser();

const onMessage = (request, sender, callback) => {
  const xhr = new XMLHttpRequest();
  const url = `https://www.google.co.jp/search?q=${encodeURI(request.query)}&num=100&safe=off&tbm=isch&source=lnms&sa=X&ved=0ahUKEwiR0sro4-zXAhVKmZQKHaJYDdEQ_AUICygC&biw=1920&bih=983&dpr=1`;
  xhr.onload = () => {
    const doc = parser.parseFromString(xhr.responseText, 'text/html');
    const divs = Array.from(doc.querySelectorAll('.rg_meta'));
    const images = divs.map(div => JSON.parse(div.innerText).ou);
    callback(images);
  };
  xhr.open('GET', url, true);
  xhr.send();
  return true;  // これがchrome extensionが非同期かどうかを判断するライン
};

chrome.extension.onMessage.addListener(onMessage);

そして表側のcontentscripts.jsにこんな感じのコードを埋め込みます

  const regex = {
    base64: /^data:/,
  };

  const onResponse = (yakulist) => {
    choiceYakuMitsuru(yakulist);
  };

  const choiceYakuMitsuru = (yakulist) => {
    const random = Math.floor(Math.random() * 101);
    const url = yakulist[random];
    if (regex.base64.test(url)) {
      choiceYakuMitsuru(yakulist);
    } else {
      replaceMemo.src = yakulist[random];
    }
  };

  chrome.extension.sendMessage({ query: 'やくみつる' }, onResponse);

これでアクセスするたびにやくみつるが変わりますね

f:id:lion_man44:20171203151705g:plain

やくみつるじゃない知らないおっさんが見てますね

調べたら野球評論家だそうで、別のコメンテーターでした

さて、一通りのchrome extensionが作れたので満足しました

webstoreに登録しようかなとも思ったんですが、クソアプリに$5はちょっと冒険が過ぎるな、と思い、やめることにしました

代わりにGitHubには置いておこうと思います

github.com

これをcloneしてきて、chromeのエクステンションに開発中のやつ食わせるだけです

簡単でしょ?




余談1:

中高生、果てはおっさんにまで有名になってしまった動画サイトを見てみたら 全てがやくみつる になったので面白かったです

f:id:lion_man44:20171213005154p:plain

共用パソコンとか使っててクソガキにエロサイト見るのはまだ早いって思うお子さんをお持ちの方は導入して見ると良いんじゃ無いでしょうか

余談2:

これすごいのが、えっちな感じのやつ入れるとど迫力な感じになってめっちゃ面白かったです

普段意識しないで見てる部分とかが違和感になって映るので、女性器やらおっぱいやらが更に強調されて見えます

あとデバッグしてて、やくみつるずっと見てるのも精神的にきついのでAV女優にしてデバッグするのは良いんですが、そのまま閉じてカフェ行かない方が良いです

嫌な汗をかきます


次のクソアプリアドベントカレンダー 14日目は @tokeikun さんです