個人的GreaseMonkey頻用パタン

# 前書き

GreaseMonkey(以下`G.M.`と呼ぶ)のスクリプトは、スクラッチで書くように何も考えずサクサクと書きたいです。
なので、私がチートシートとして使うために、実際によく使っているパターンをまとめておきます。

# 本文

## 初期化処理
G.M.を使うときに私は、windowのloadイベントのリスナーを追加して、やりたいことを記述した関数をそこで実行するようにしています。

```javascript
// ==UserScript==
--- 省略 ---
// ==UserScript==

//////////////////////////////////////////////////////////////////////////////////////////////
// ここに直に書くと、DOMが読み込まれる前に実行されるので、addEventListenerと関数/変数定義以外は記述しない。
// なぜなら作ってきた大抵のG.M.スクリプトは、特定ページのDOMに対して処理を行っているから。

let v1 = 0;
const f1 = async function () {/*やりたいことの実装はここに書く*/ }
const f2 = async function () {/*やりたいことの実装はここに書く*/ }

async function exec_workflow() {
    // やりたいことの流れはここに記述する。
    await f1(); // awaitキーワードついているものは上から順に実行される(同期処理)。
    await f2();

    // タイマー処理の登録もここで行う。
    var waitTime = 1000 * 60 * 3; // 1000(millisec) * 60(sec) * 3 = 3分
    setTimeout(f2, waitTime); // 三分後に実行する
}
//////////////////////////////////////////////////////////////////////////////////////////////

// メイン処理の実行ﾀｲﾐﾝｸﾞが、windowのロード時となるように登録する
window.addEventListener('load', async function () { await execWorkflow() });
```

## タイマー処理

### 指定時間後に実行する

```javascript
var waitTime = 1000 * 60 * 3; // 1000(millisec) * 60(sec) * 3 = 3分
setTimeout(f1, waitTime); // 三分後にf1を実行する 
```

### 指定時間間隔で実行する

```javascript
var intervalTime = 1000 * 60 * 3; // 1000(millisec) * 60(sec) * 3 = 3分
setInterval(f1, intervalTime); // 三分間隔でf1を実行する 
```

## 同期処理
スクリプトの上から下に順番に処理を実行したいときに使うパタンです。
下記の投稿に同期処理のテンプレを書き留めておきました。
[javascript同期処理用の簡単なテンプレ](https://qiita.com/cp3/items/d0e7e698f53ebd336bce)

知っている人にとっては当たり前ですが、javascriptは非同期処理がデフォルトです。
なので上から下に順番に実行されるとは限りません。

javascriptは全然使わないので、これを知らずにハマってしまったことがありました。

## DOM関連

### ページ先頭に要素を追加する

ページ内のどの要素よりも前に或る要素を追加したいということがあります。
(e.g. ページ内の目次をページの一番上に表示したい)

下記はそんな時によく使っているスクリプトです。

```javascript

function prependElementBody(strElement) {
  // 要素を作成する
  let newElm = document.createElement(strElement);

  // 要素に好きなプロパティをセットする
  newElm.innerText = "new element";

  // 要素をページの先頭に追加する
  document.body.prepend(newElm);
}

/// 使用例
const s = "div"; // divタグ。タグとして使用するのはdocument.createElement(s)のsにあたる文字列。"button"にすればボタンを作成できる。
prependElementBody(s);
```

＊厳密に言うと、行っていることは「ページ先頭に追加する」ではなく、「bodyタグの子要素リストの先頭に追加する」です。動作しなかったら要確認です。

### ボタンを追加する
基本的に私は、スクリプトを走らせるページ上の要素を利用して、その要素からの相対位置を指定してボタンを追加しています。
e.g. もし、ホームページアイコン(QiitaならQiitaのアイコンが左上にある)などの横に追加したいならば、そのアイコンの親要素のchildノードとしてボタンを追加します。

```javascript
function add_button(){
    let button = document.createElement('button');
    button.innerText='ボタン';
    button.onclick = function() { f3(); };
    document.querySelector("開発ツールなどで特定したCSSセレクタを書く").appendChild(button);
}
```

## リクエスト関連

### JSONを取得する

GM.xmlHttpRequestはawaitできません。これはネックだと思います。
加えて、2022年現在にも関わらず公式のドキュメントのこのメソッドの説明は2017年のままです。
そのドキュメントのsynchronous:trueにしたときの挙動の説明が誤っているのも気になります。
"more data will be available in the return value."とあるがreturn値は常に`undefined`です。
(指摘する人が2018年に既に他サイトに現れていますが、公式には正す人居ないようです。)

```javascript
// ==UserScript==
---略---
// @grant GM.xmlHttpRequest
// ==/UserScript==

---略---
//
let parsedResponce;
GM.xmlHttpRequest({
    // synchronous: true, //同期処理のためのオプションだが、機能しなかったのでコメントアウトした。
    method: 'GET',
    url: 'https://sample.com',
    onload: function (response) {
        console.log("got response.");
        parsedResponse = JSON.parse(response.responseText);
    }
});

/*
ここにsleepを入れなければ、なぜか順序が狂った。
つまり、先に下のconsole.log("got value.")が実行され、そのあとにxmlHttpRequest内のconsole.log("got response.");が実行された。
なのでsleepを入れている。ちなみに1秒にせず0秒にしても順序が狂った。
順番が前後しても値はなぜか格納できているようなので問題は今のところないが、気持ち悪いのでsleepを念のために入れている。
この挙動に関係あるかもしれないドキュメント -> https://wiki.greasespot.net/Troubleshooting_(Script_Authors)#Variables_change_before_a_GM_xmlhttpRequest_or_setTimeout_callback_runs
*/
await sleep(1); 

console.log("got value.");

```

[このやり方](https://drgqst.hatenadiary.org/entry/20080110/1199976698)のほうが良いかもしれません。

## ボイラープレート

これまで書いてきたことを踏まえたユーザスクリプト用の軽いテンプレート

```javascript
// ==UserScript==
--- 省略 ---
// ==UserScript==

////////// ヘルパー
//second秒待機する
const sleep = second => new Promise(resolve => setTimeout(resolve, second * 1000));

function log(str) {
    console.log(str);
}
//////////

//////////////////////////////////////////////////////////////////////////////////////////////
// ここに直に書くと、DOMが読み込まれる前に実行されるので、addEventListenerと関数/変数定義以外は記述しない。
// なぜなら作ってきた大抵のG.M.スクリプトは、特定ページのDOMに対して処理を行っているから。

let v1 = 0;
const f1 = async function () {/*やりたいことの実装はここに書く*/ }
const f2 = async function () {/*やりたいことの実装はここに書く*/ }

async function execWorkflow() {
    // やりたいことの流れはここに記述する。
    await f1(); // awaitキーワードついているものは上から順に実行される(同期処理)。
    await f2();

    // タイマー処理の登録もここで行う。
    //指定時間後に実行する
    var waitTime = 1000 * 60 * 3; // 1000(millisec) * 60(sec) * 3 = 3分
    setTimeout(f2, waitTime); // 三分後に実行する
    //指定時間間隔で実行する
    var intervalTime = 1000 * 60 * 3; // 1000(millisec) * 60(sec) * 3 = 3分
    setInterval(f1, intervalTime); // 三分間隔でf1を実行する 
}
//////////////////////////////////////////////////////////////////////////////////////////////

// メイン処理の実行ﾀｲﾐﾝｸﾞが、windowのロード時となるように登録する
window.addEventListener('load', async function () { await execWorkflow() });

```

# おまけ

## javascript命名規則

下記の規則をなるべく使いたいです。

```
    functionNamesLikeThis
    variableNamesLikeThis
    ClassNamesLikeThis
    EnumNamesLikeThis
    methodNamesLikeThis
    CONSTANT_VALUES_LIKE_THIS
    foo.namespaceNamesLikeThis.bar
    filenameslikethis.js
```

["Google JavaScript Style Guide 和訳" by cou292 is licensed underCC BY 3.0](https://cou929.nu/data/google_javascript_style_guide/#javascript-style-rules)

## エディタについて

G.M.組み込みエディタ(全体的に貧弱、あとライトテーマしかないので目がチカチカします)が使いづらかったら、[このリンク先の方法](https://klim0824.hatenablog.com/entry/2018/08/05/052152)でVSCodeなどの外部エディタで編集することが有効かもしれません。
ただ、G.M.は多分@requireでローカルファイルを読み込めないので注意が必要です。

私は、localhostにFTPサーバ立てて、jsファイルを提供するやり方でやっています(`@require http://localhost:123123/userscript.js`みたいにしています)。


# 最後に

* 今後、この投稿には参考になるリンク集なども追記したいです。やはりMDNが頼りになります。
* G.M.を使う上でお世話になるFirefox開発者ツールの頻用操作などもここに一緒に書き留めたいです。
* 別投稿として、G.M.用テンプレートを置いておきたいです。

## 参考リンク

* [GreaseMonkey の GM_xmlhttpRequest を順番に同期とりながら実行する](https://drgqst.hatenadiary.org/entry/20080110/1199976698)
* [UserScriptをお気に入りのエディター/IDEで編集する方法 @Tampermonkey](https://klim0824.hatenablog.com/entry/2018/08/05/052152)