node.js+Express+pugでDBDのマップカウンターを作る

<h2>はじめに</h2>
<p>DBDという対人ゲームで、あるキャラクターを使うと苦手なマップばかり当たるので、猜疑心からマップカウンターを作りました。（なお結果としては私が苦手なマップがDBDに多いだけでした！）</p>

<h3>目次</h3>
<ol>
<li>node.js+Express+pugインストール</li>
<li>DBDのマップをスクリピング</li>
<li>JSONの読み書き</li>
<li>動的リンクを扱う</li>
<li>.pugへ変数を渡す</li>
<li>.pugでhtmlを生成</li>
<li>cssの記述</li>
</ol>

<h3>1.node.js+Express+pugインストール</h3>
<p><a href="https://expressjs.com/en/starter/generator.html">ここ</a>を見ながらインストール</p>

<h3>2.DBDのマップをスクレイピング</h3>
<p>稚拙なソースですが動きます！</p>
```python:dbdmap.py
from time import sleep
import json

def main():
    import requests
    from bs4 import BeautifulSoup

    FullURL = "https://deadbydaylight.fandom.com"
    URL = "https://deadbydaylight.fandom.com/wiki/Dead_by_Daylight_Wiki"
    page = requests.get(URL)

    soup = BeautifulSoup(page.content, "html.parser")

    results = soup.find(id="fpRealms")
    results = results.find("div", {"class": "fplinks"})
    results = results.find_all("a")

    maplst = []

    mapdict = {}

    for result in results:
        dbdmap = result["href"]
        if not dbdmap in maplst:
            maplst.append(dbdmap)

    for mapdbd in maplst:
        mapurl = FullURL + mapdbd
        mappage = requests.get(mapurl)
        mapsoup = BeautifulSoup(mappage.content, "html.parser")

        mapresults = mapsoup.find("table", {"class": "wikitable"})
        mapresults = mapresults.find("tbody")
        mapresults = mapresults.find("tr")
        mapresults = mapresults.find_all("td")

        for mapresult in mapresults:
            mapresult = mapresult.find("center")
            mapresult = mapresult.find("a")
            mapdict[mapresult.text.strip()] = 0

        sleep(3)

    with open('<保存したい場所>.json', 'w') as outfile:
        json.dump(mapdict, outfile)
    
if __name__ == "__main__":
    main()
```

<p>これにより以下の様なJSONを得られる。</p>
```
{"Coal Tower": 0, "Groaning Storehouse": 0, "Ironworks of Misery": 0, ...
```

<h3>3.JSONの読み書き</h3>
<p>ここで詰まったのでいくつか注意点を書いておきます</p>
<p>jsonはクライアント側からは読み書きできません。</p>
<p>つまり生成されたウェブページに組み込まれたスクリプトからは操作できないです。</p>そのためサーバー側で操作する必要があります。</p>
<p>また、サーバー側での操作はroutes/index.jsonで行います。（リクエストされたページに対応したファイル）</p>

```
const fs = require('fs');
const filePath = "./routes/dbdmap.json"

//jsonを読み取る
const dbdmaplst = require("./dbdmap.json");

//jsonに書き込む
fs.writeFile(filePath, JSON.stringify(dbdmaplst), (err) => {
  if (err) console.log('Error writing file:', err)
})
```

<h3>4.動的リンクを扱う</h3>
<p>カウンターなので、index/"map名"/"up/down"のような動的リンクを扱えるようにします。</p>
<p>複数個設定できます。</p>
```
router.get('/', function(req, res, next) {

router.get('/:map/:vote', function(req, res, next) {
```
<p>受け取ったリンクのキーワードを引数のように扱えます</p>
```
router.get('/:map/:vote', function(req, res, next) {
  const dbdmaplst = require("./dbdmap.json");
  const vote = req.params.vote;
  if (vote == "up") {
    dbdmaplst[req.params.map] += 1;
  }
  else if (vote == "down") {
    if (dbdmaplst[req.params.map] > 0) {
      dbdmaplst[req.params.map] -= 1;
    }
  }
```
<h3>5.pugへ変数を渡す</h3>
<p>json形式で変数を渡します。</p>
```
res.render('index', { title: 'Epress', dbdmap: dbdmaplst});
```

<p>以上index.jsの完全なソースはこちら</p>
```javascript:index.js
var express = require('express');
var router = express.Router();

//read json
const fs = require('fs');
const filePath = "./routes/dbdmap.json"

/* GET home page. */
router.get('/', function(req, res, next) {
  const dbdmaplst = require("./dbdmap.json");
  res.render('index', { title: 'Epress', dbdmap: dbdmaplst});
});

router.get('/:map/:vote', function(req, res, next) {
  const dbdmaplst = require("./dbdmap.json");
  const vote = req.params.vote;
  if (vote == "up") {
    dbdmaplst[req.params.map] += 1;
  }
  else if (vote == "down") {
    if (dbdmaplst[req.params.map] > 0) {
      dbdmaplst[req.params.map] -= 1;
    }
  }

  fs.writeFile(filePath, JSON.stringify(dbdmaplst), (err) => {
    if (err) console.log('Error writing file:', err)
  })

  res.render('index', { title: 'DBD MAP LOG', dbdmap: dbdmaplst});
});
module.exports = router;

```

<h3>6 .pugでhtmlを生成</h3>
<p>pugはpythonのようにインデントで管理されているようです。</p>
<p>以下ソース内に注意点を記載</p>
```pug:index.pug
//layout.pugをテンプレートに使う
extends layout

//テンプレート内のblock contentに置き換えられる
block content
  //'-'を先頭に記載することでjavascriptを組み込むことができる。
  -var count = 0;
  div(class="tbl")
    table(cellpadding=10)
      //pug 独自のfor/while/if-else文を扱える
      //ここの'dbdmap'はindex.jsから受け取ったもの
      each val, key in dbdmap
        -count += val;
        tr 
          //'#{}'で変数を扱うことができる。
          td #{val}
          td #{key}
          td
            a(href= "/" + key + "/up") up
          td 
            a(href= "/" + key + "/down") down
      
      h2 Match count: #{count}
```
<p>pugの記述方法は<a href="https://pugjs.org/api/getting-started.html">こちら</a></p>

<h3>7.cssの記述</h3>
<p>cssよく分からないのですが、こんな感じにしたらいい感じになりました。。。</p>
```css:style.css
body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

table, tr, td {
  border: 1px solid black;
  padding: 10px;
}

table {
  display: inline-block;
  float: left;
}
```
<p>cssの読み込み方。テンプレートのlayout.pugに記載する。</p>
```pug:layout.pug
doctype html
html
  head
    title= title
  body
    block content
    //javascript
    script(src='/javascripts/todo.js')
    //css
    link(rel='stylesheet', href='/stylesheets/style.css')
```


<h3>終わりに</h3>
![DBD MAP LOG — Mozilla Firefox 2021_07_24 12_33_16 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148969/fb99001a-cd30-fc84-5834-cac39ee93dca.png)

<p>以上すべてのソースコードを記載したわけではありませんが、詰まる恐れがある点について解説できました。</p>
<p>もし質問があればどうぞコメントにどうぞ。</p>