Chrome拡張作ったら異世界転生した件

# 詠唱

配列(文字列)からランダムに要素を取り出すコードを書くじゃろ

```js
// ランダムに要素を取り出す関数
const randomPick = (items) => {
  const index = Math.floor(items.length * Math.random());
  return items[index];
};

randomPick("123456"); // 1〜6のどれか
```

文字列の範囲を展開するコードを書くじゃろ

```js
// 文字列の範囲を展開する関数
const expandCharRange = (str) =>
  str.replace(/.-./g, (range = 'a-z') => {
    // ハイフンを挟んだ文字列を全て置換
    const first = range.codePointAt(0); // 1文字目の文字コード
    const last = range.codePointAt(2); // 2文字目の文字コード
    const length = last - first + 1; // 文字数
    const codePoints = [...Array(length)].map((_, i) => first + i); // 全ての文字コード
    return String.fromCodePoint(...codePoints); // 文字列に変換
  });

expandCharRange("a-z0-9")； // "abcdefghijklmnopqrstuvwxyz0123456789"
```



ルーン文字をランダムで生成して...

```js
// ルーン文字を生成する関数
const runes = expandCharRange('ᚠ-ᛪᛮ-ᛰᛱ-ᛳᛴ-ᛸ');
const randomRune = () => randomPick(runes);

[...Array(3)].map(randomRune) // ["ᛴ", "ᚶ", "ᚳ"]
```

レイアウトが崩れたりしないようにテキストノードだけ置換するコードを書くんじゃ

```js
// 指定要素以下の全ノードを取得する関数
const allNodes = (root = document.body) => [root, ...[...root.childNodes].flatMap(allNodes)];

// 無害そうなテキストノードだけ取得
const allVisibleTextNodes = allNodes()
  .filter((node) => node.nodeType === Node.TEXT_NODE) // テキストノードだけ抽出
  .filter((node) => !/script|style/i.test(node.parentElement.tagName)) // 親がヤバいヤツは除外
  .filter((node) => node.nodeValue.trim()); // 空っぽなヤツは除外

// 空白以外は全てルーン文字に置換
allVisibleTextNodes.forEach((node) => {
  node.nodeValue = node.nodeValue.replace(/\S/g, randomRune);
});
```

# Chrome拡張を作成 (必要なファイルは2つだけ)

2つのファイルを作って適当なフォルダにぶち込む

```json:manifest.json
{
  "manifest_version": 3,
  "version": "0.1.0",
  "name": "異世界拡張",
  "description": "Chrome拡張作ったら異世界転生した件",
  "background": {
    "service_worker": "isekai.js"
  },
  "action": {
    "default_title": "転生する"
  },
  "permissions": ["activeTab", "scripting"]
}

```

```js:isekai.js
'use strict';

// 上記のまとめ関数 (若干変えた)
function tensei() {
  // ランダムに要素を取り出す関数
  const randomPick = (items) => {
    const index = Math.floor(items.length * Math.random());
    return items[index];
  };

  // 文字列の範囲を展開する関数
  const expandCharRange = (str) =>
    str.replace(/.-./g, (range = 'a-z') => {
      // ハイフンを挟んだ文字列を全て置換
      const first = range.codePointAt(0); // 1文字目の文字コード
      const last = range.codePointAt(2); // 2文字目の文字コード
      const length = last - first + 1; // 文字数
      const codePoints = [...Array(length)].map((_, i) => first + i); // 全ての文字コード
      return String.fromCodePoint(...codePoints); // 文字列に変換
    });

  // ルーン文字を生成する関数
  const runes = expandCharRange('ᚠ-ᛪᛮ-ᛰᛱ-ᛳᛴ-ᛸ');
  const randomRune = () => randomPick(runes);

  // 空白・記号・絵文字・数字・ギリシャ文字以外をルーン文字に置換する関数
  const conv = (str) => str.replace(/[^\p{White_Space}\p{Symbol}\p{Punctuation}\p{Emoji}\p{Number}\p{sc=Greek}]/gu, randomRune);

  // 指定要素以下の全ノードを取得する関数
  const allNodes = (root = document.body) => [root, ...[...root.childNodes].flatMap(allNodes)];

  // 無害そうなテキストノードだけ取得
  const allVisibleTextNodes = allNodes()
    .filter((node) => node.nodeType === Node.TEXT_NODE) // テキストノードだけ抽出
    .filter((node) => !/script|style/i.test(node.parentElement.tagName)) // 親がヤバいヤツは除外
    .filter((node) => node.nodeValue.trim()); // 空っぽなヤツは除外

  // ルーン・オリジナルを切り替え
  allVisibleTextNodes.forEach((node) => {
    if (node.original) {
      [node.original, node.nodeValue] = ['', node.original];
    } else {
      [node.original, node.nodeValue] = [node.nodeValue, conv(node.nodeValue)];
    }
  });
}

// 拡張のアイコンを押すたびに実行
chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    function: tensei
  });
});
```

Chromeの拡張機能のページを開いてデベロッパーモードを有効化
「パッケージ化されていない拡張機能を読み込む」からフォルダを選択して完了じゃ！

![スクリーンショット 2021-06-21 10.21.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18902/819325f5-f052-1c10-139d-b50fb90d47ed.png)

# 結果

![スクリーンショット 2021-06-21 10.22.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18902/4f00b2b6-603d-7fcf-6628-c4740c0ce3e2.png)

右上のパズルのピースみたいなアイコンから「異世界拡張」をクリックすればいつでも異世界
楽しい！✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌

# 参考

- [ルーン文字 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%BC%E3%83%B3%E6%96%87%E5%AD%97)
- [Unicode ルーン文字 - CyberLibrarian](http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/u16a0.html)