メルカリの検索結果からSOLDを削除するUser Script

# どんな講釈をヌかそうと、要らないものは要らない。
・メルカリが検索結果に「売れた商品」も表示するのはなぜ？　商品検索におけるUI/UXの考え方 - ログミーTech
https://logmi.jp/tech/articles/321231

> 邪魔だから消してみると何が起きるかと言うと、全KPIがすべて下がります。並みの勢いじゃないですよ。出品転換率、つまり1人のお客様が出品行動を取ったという行動も減るし、買ったという行動も減ります。
> 
> その結果、結局取引総数も取引総額もすべて下がって、負のスパイラルに入る。それで慌てて元に戻すというのを、実はUSとJPを合わせて、4～5回は繰り返しているんじゃないかと思います（笑）。
> 
> いったい、なぜなのか？　長らく謎でした。ちなみに創業者の山田進太郎は「ユーザーインタビューとかで、邪魔だからと言われているので消してもいいですよね？」と言われたとき、「いや、俺はあったほうがいいと思うけどね」と言ったそうです。
> 
> 「なんか知らないけど、あったほうがいいと思う」と言ったと。3年前くらいですね。直感的にわかっている感じでした、あの人は（笑）。予言していたんです。
> 
> 僕は「こういうことなんだろうな」という仮説があるんです。検索改善をしていて、僕がうまくいったときというのは、必ずさっきの購買転換率が上がるんです。
> 
> 「やった！　検索結果がよくなって統計上有意に買う人が増えた！　買う数が増えた！」と思うと同時に、出品する人も増えるんですね。「なんで検索改善しているのに出品する人が増えるんだ？　俺は出品を増やそうとはしていないんだけど」と思っていました。

:thinking::thinking::thinking::thinking::thinking::thinking::thinking::thinking::thinking::thinking::thinking:

> 結局、行き着いた答えが「出品しようとしたときに検索してるんだ」ということです。つまり、買おうとする人もいるし、売ろうとする人もいる。Nintendo Switchを持っている人が売ろうとしたときに、「Nintendo Switch」と検索して何を見ているかという話ですね。

転売ヤーの気持ちなんか知るかヴォケ。
真っ当な購買には最低のUXですね。

# 環境
* Tampermonkey
* メルカリの検索結果

# User Script
## 案① 検索結果から売り切れを削除する.
```javascript:User&#x20;Script
// ==UserScript==
// @name         案① 検索結果から売り切れを削除する.
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.mercari.com/jp/search/?*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    // Your code here...
    document.querySelectorAll('div.items-box-content section').forEach(function(section) {
        if(section.querySelectorAll('div.item-sold-out-badge').length > 0) {
            section.parentNode.removeChild(section);
        }
    });
})();
```
安心感が強い半面、詳細検索と共存出来ない残念さがある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/8189f2f2-78f2-9c5a-de65-85538d8b929f.png)
``div.item-sold-out-badge``のある ``section`` を削除する。

## 案② 画面移動
パラメータが ``keyword`` のみの場合、画面上部からの検索だと判定し、その場合には ``販売中`` の検索条件を追加した場合のURLに移動する。

```javascript:User&#x20;Script
// ==UserScript==
// @name         案② 画面移動.
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.mercari.com/jp/search/?*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
    let querys = [...new URLSearchParams(window.location.search).entries()].reduce(function(obj, e) {return {...obj, [e[0]]: e[1]}}, {});
    console.info(Object.keys(querys).length);
    if(Object.keys(querys).length == 1) {
        if(querys.keyword) {
            location.replace(location.href + '&status_on_sale=1');
        }
    }
})();
```
* before  
https://www.mercari.com/jp/search/?keyword=PS5
* after  
https://www.mercari.com/jp/search/?keyword=PS5&status_on_sale=1

JavaScriptでQuery Stringを解釈する楽しさはあるが、全体的にはナンセンス。


## 案③ デフォルトの検索条件に付与 (1shot)
``input`` の ``type`` が ``search`` のものを画面上部の検索ボックスとして扱い、同 ``form`` 中に ``販売中`` の検索条件のための ``hidden`` を追加する。

```javascript:User&#x20;Script
// ==UserScript==
// @name         案③ デフォルトの検索条件に付与 (1shot)
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.mercari.com/*
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
    setTimeout(function() {
        document.querySelectorAll('input[type="search"]').forEach(function(e) {
            let p = e.parentNode;
            let div = document.createElement('div');
            let f = function (key, val) {
                let input = document.createElement('input');
                input.type = 'hidden';
                input.name = key;
                input.value = val;
                return input;
            };
            div.appendChild(f('status_on_sale', '1'));
            p.removeChild(e);
            div.appendChild(e);
            p.prepend(div);
        });
    }, 1);
})();
```

* before  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/3697b3cf-9bcb-a30e-765f-3fef1c491004.png)
* after  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/0e5d9b65-4b3e-23c8-7dd4-f15e169faf21.png)

読み込み後の謎な処理で、form内のエレメントが2つに戻されるクソみたいな挙動があるので。独自タグでwarpして退避している。たまにうまく反応してくれないときもあるので要調整。

## 案④ デフォルトの検索条件に付与 (observer)
``input`` の ``type`` が ``search`` のものを画面上部の検索ボックスとして扱い、同 ``form`` 中に ``販売中`` の検索条件のための ``hidden`` を追加する。
案③だと、たまに戻されてしまうので、親ノードを監視して再設定させる。 (多重で付かない様に、無かったら追加する。)

```javascript:User&#x20;Script
// ==UserScript==
// @name         案④ デフォルトの検索条件に付与 (observer)
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.mercari.com/*
// ==/UserScript==
(function() {
    'use strict';

    // Your code here...
    let f = function(p) {
        let div = document.createElement('div');
        let a = function(name, val) {
            if(p.querySelectorAll('input[name="' + name + '"]').length == 0) {
                let input = document.createElement('input');
                input.type = 'hidden';
                input.name = name;
                input.value = val;
                p.appendChild(input);
            }
        }
        a('status_on_sale', '1');
    }


    document.querySelectorAll('input[type="search').forEach(function(e) {
        let p = e.parentNode;
        // first
        (function() {
            f(p);
        })();
        // observer
        (function() {
            let observe;
            let observer = new MutationObserver(function() {
                observer.disconnect();
                f(p);
                observe();
            });
            observe = function() {
                observer.observe(p, {
                    childList:true,
                });
            }
            observe();
        })();
    });
})();
```
* before  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/3697b3cf-9bcb-a30e-765f-3fef1c491004.png)
* after  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/84c4087e-20df-6bd9-d85b-47d57ed23953.png)


案③のformエレメントに追加しても削除されてしまう問題について、observerで潰した版。
詳細検索と衝突せず、TOP並びに検索画面に埋め込めた。一番美しい気がする。

# 以上。

# 参考 (謝辞)
* JavaScriptのMutationObserverでDOMの変化を監視する方法 - Qiita  
  https://qiita.com/munieru_jp/items/a6f1433652124a2165e4