ZONeチャレンジ第一弾をJavaScriptでハックした話

## 要約

キャンペーンサイトへPCからスマホサイズでアクセスし、WebブラウザのコンソールからJavaScriptでフリック入力を再現した。

ソースコード: https://github.com/r-yanyo/flick-challenge/blob/master/newfuga.js

キャンペーンサイト: https://cp.zone-energy.jp/challenge/kuma/

キャンペーンサイトのconsoleにソースコードを貼り付けると（途中まで）フリック入力が自動的に入力されます（途中までの理由は最後の方に書いてます）。

## PCでスマートフォン専用サイトにアクセスする

![PCでアクセスした図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176467/6cf1a7c4-1802-09b9-7801-653f06bb44a5.png)

PCでキャンペーンページを普通に開くと、スマートフォン専用サイトである旨が表示されます。
このままではPCでフリック入力ゲームで遊ぶことが出来ません。

同じWebサイトでも、PCでアクセスした時とスマートフォンでアクセスした時にWebページのデザインが違う経験があるのではないでしょうか？これは、デバイスに依存してWebサイトの表示を変更する場合に、一般的に[**メディアクエリ**](https://developer.mozilla.org/ja/docs/Web/CSS/Media_Queries/Using_media_queries) が基準として使用されるからです。

メディアクエリはユーザが使用しているデバイスの`Viewport`（ウィンドウのサイズ）を基準として、Webサイトの表示を変更する方法です。つまり、実際に**PCやスマートフォンのどちらを使用しているかは検証しておらず、ウィンドウのサイズだけで判断**しています。よって、何らかの方法でウィンドウサイズを変更すれば、PCでもスマートフォン専用ページにアクセスすることが出来ます。

Google Chromeでは、デベロッパーツールを開き、Device Toolbarを使用することでウィンドウのサイズをスマートフォンサイズに変更できます。

## フリック入力をプログラミングで再現する

大きく分けて工程は3つあります。
**1. ひらがなボタンのElementを取得**
**2. 取得したElementに対してフリック入力を再現**
**3. 答えを機械が認識できる形で入力＆変換**


### 1. ひらがなボタンのElementを取得

![ボタンをインスペクト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176467/42f0746a-8227-ffb7-069c-bada9c97ee7b.png)

デベロッパーツールで見てみると、どうやら`.css-xx1yz5-buttonStyle`というクラスが各「あかさたなはまやらわ」ボタンに付与されているようです。
さらに、そのボタンをタップすると出現するフリック入力部分（"あ"をタップした場合は「あいうえお」）には`.css-2rzvda-wingStyle`というクラスが付与されています。

このことから、例えばキーボードの「さ」を取得したいときは、
`[0]: "あ", [1]: "か", [2]: "さ"`
なので

``` ts
document.getElementsByClassName("css-xx1yz5-buttonStyle")[2]
```

フリック入力の「す」を取得したいときは、「す」は上から13番目なので

``` ts
document.getElementsByClassName("css-2rzvda-wingStyle")[12]
```

とします。

ひらがな以外の文字に関しては、デベロッパーツールでその要素が上から何個目にあるかを見れば分かります。

### 2. 取得したElementに対してフリック入力を再現
初めは[Clickイベント](https://developer.mozilla.org/ja/docs/Web/API/Element/click_event)でフリック入力が再現されると思ったのですが、
`document.getElementsByClassName("css-xx1yz5-buttonStyle")[2].click()`
のようにしても何も起こりませんでした。

そこで次に、[TouchEvent](https://developer.mozilla.org/ja/docs/Web/API/TouchEvent) を試したところ、キーボードのボタンがタッチされるイベントが発生しました。よって、このTouchEventを使うことにしました。

``` touchEvent.ts
//elmはフリックの起点となるelement。「す」を入力するときは「さ」
//moveElmはフリック先のelement。「す」を入力するときは「す」

touchMove = function (elm, moveElm) {
  if (!moveElm) return;
  rect = moveElm.getBoundingClientRect();
  X = rect.x + rect.width / 2;
  Y = rect.y + rect.height / 2;
  let touch = new Touch({
    identifier: Date.now(),
    target: elm,
    clientX: X,
    clientY: Y,
    force: 1,
    pageX: X,
    pageY: Y,
    radiusX: 41.66666793823242,
    radiusY: 41.66666793823242,
    rotationAngle: 0,
  });
  touchMoveEvent = new TouchEvent("touchmove", {
    bubbles: true,
    touches: [touch],
  });
  elm.dispatchEvent(touchMoveEvent);
};
```
TouchEventに関してはあまり詳しくなかったため、実際にイベントを発生させて必要な部分だけを変更する戦略を取りました。
結果的には `clientX`,`clietnY`,`pageX`,`pageY` の数値を**フリック先のelementの座標**に変えることで、フリック入力を再現することが出来ました。

フリック入力をするときにTouchEventで発生するイベントは、`touchstart` -> `touchmove` -> `touchend` の順で発生します。また、それぞれのイベントの発生元は全て**フリックの起点となるelement**で発火します。[^1]

```ts
//elmはフリックの起点となるelement。「す」を入力するときは「さ」
//moveElmはフリック先のelement。「す」を入力するときは「す」
function touchEmulate(startElement, moveElm) {
  touchStart(elm);
  touchMove(elm, moveElm); //イベント発火元はelm
  touchEnd(elm);
}
```

### 3. 答えを機械が認識できる形で入力＆変換
「ひらがな」から数字への変換は愚直に一つ一つ入力しました。

```ts
"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもや（ゆ）よらりるれろわをんー　、。？！".split("")
```

例えば「す」をフリック入力したい場合は、

```flick.ts
//「さ」ボタン
const button = document.getElementsByClassName("css-xx1yz5-buttonStyle")[2];
//「す」フリック
const flick = document.getElementsByClassName("css-xx1yz5-buttonStyle")[12];
```

このように「あ」〜「ん」や「！？」の記号などは入力できるようになりました。
ここで問題となるのが、濁音や半濁音、促音（「が」、「ぱ」、「っ」など）の入力です。これらはフリック入力を2回行う必要があります。例えば、「が」であれば、「か」をフリックした後に「゛」をフリックする必要があります。
これを解決する方法は色々あるのですが、今回は**全部条件分岐**で書きました（具体的にはソースコード参照）。[^2]

「これで晴れてフリック入力がエミュレート出来た・・・」と思ったのですが、もう1つ問題があります。
フリック入力では同じボタンを2回押すと次の文字が表れるという特性上、連続で同じ文字を入力しようとすると正しい文字が入力されません。
例えば、「けたたましく」を入力しようとすると「けちましく」になってしまいます。
この問題はキーボードにある「→」ボタンを入力すれば解決できますが、当時の私は気付かなかったため**手動で入力を分けて**解決しました（ダサい・・・）。


### おわりに
以上でフリック入力をエミュレートすることが出来ました。便宜上「ハックした」と書いていますが、JavaScriptでフリック入力をエミュレートしただけです。
キャンペーンは終了してしまいましたが、まだゲームで遊ぶことはできるようなので、是非実際に試してみてください。


[^1]: フリック入力周りの動作は実際のイベントを複製して改変したため、あまり理解していない部分が多々あります。
[^2]: 「が」を「か」と「゛」に分割する方法は、もっと良い方法があると思います。ひらがなの部分も含め、文字コードを上手く使えばもっと綺麗に書けるかも？