【Nicehash API+Nodejs+LINE Notify】マイニングリグのステータスを24時間監視してLINEに通知する

# まえがき

グラボ4枚でマイニングしているんですがパソコンの調子が悪いときは急に落ちたり（電源は入った状態）、ネット回線の影響でマイニングできてなかったりします。

これを心配して外出先で毎回Nicehashのダッシュボード見るのも疲れたので、エラーのときやエラー回復したときだけLINEで通知してくれたらいいなと思って作ったものをまとめました。

https://twitter.com/nodokamome/status/1449172789873676293?s=20

# 必要なもの

各トークンの取得方法については割愛します。ググればだいたい出てきます。

1. Nicehash APIのアクセストークン
   - https://www.nicehash.com/docs/ 
1. LINE Notifyのアクセストークン
    - https://qiita.com/iitenkida7/items/576a8226ba6584864d95#line%E3%81%AE%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%A7-notify-%E3%81%AE%E7%99%BB%E9%8C%B2
1. Nicehash公式のサンプルプロジェクト
(こちらは作業手順を説明します)
    - https://github.com/nicehash/rest-clients-demo

# フォルダ構成・環境準備

`nicehash-line-test`というフォルダを作成し、環境準備します（フォルダ名はお好みでも大丈夫です）

```bash
$ mkdir nicehash-line-test
$ mkdir nicehash-line-test/src
```

## 公式のRESTクライアントのサンプルをcloneし、ファイルをコピー

Nicehash APIをRESTで使用するにはHTTPヘッダーにいろいろ追加してあげる必要がありますが、全て自力で書くのはちょっと辛いので、公式がGithubで公開されているRESTクライアントを使うと簡単に実装できます。

https://github.com/nicehash/rest-clients-demo

レポジトリには様々な言語のクライアントがありますが、今回はJavaScriptで実装するので、`javascript`から必要なファイルを新しいフォルダにコピーしてください。

```bash
# ローカルにRESTクライアントをcloneする
$ git clone git@github.com:nicehash/rest-clients-demo.git

# cloneしてきたjavaScriptフォルダからapi.jsとconfig.jsをsrc/へコピーします
$ cp rest-clients-demo/javascript/api.js nicehash-line-test/src
$ cp rest-clients-demo/javascript/config.js nicehash-line-test/src
```

自力で書く場合のHTTPヘッダーについては公式ドキュメントにあるのでぜひ参考にしてください。

https://www.nicehash.com/docs/

## 必要なファイルを作成

最終的に以下の構成になります。事前に必要なファイルを作成しておきます。

```bash:サンプルプロジェクト
  （プロジェクトルートディレクトリ）/
  ├ src/
  |  ├ api.js               # ※公式からコピーしてきたもの※ Nicehash APIを使うためのラッパー
  |  ├ line.js              # Line Notifyに通知を行う
  |  ├ rigStatus.js         # マイニングリグのステータスを返す
  |  └ config.js            # ※公式からコピーしてきたもの※ Nicehash APIの環境変数ファイル
  ├ index.js                # 実行ファイル。通知する条件のロジックや通知頻度の設定
  ├ .env　　　　             # Line Notifyのトークンを記載する
  └ .package.json           # モジュールインストールする

# config.jsと.envにはトークン情報があるので取り扱いに注意してください
```

サンプルプロジェクトの構成になるようにコマンドで必要なファイルを作成します。
```bash
$ cd nicehash-line-test

# package.jsonの作成
$ npm init -y
Wrote to /nicehash-line-test/package.json:

{
  "name": "nicehash-line-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

# ファイルを事前に作成しておく
$ touch .env index.js src/line.js src/rigStatus.js
```

## npmモジュールのインストール

後の作業に必要なnpmモジュールをインストールしておきます。

```bash
$ npm install --save request request-promise request-promise-native esm dotenv axios crypto-js querystring node-cron
```

# リグのステータスを取得するテスト

まずはマイニングリグのステータスを取得APIを叩けること確認します。

## 環境変数を設定する

Nicehashのダッシュボードから以下に情報を入力してください。

`src/config.js`にNicehashAPIを使うために必要な情報を入力します。

```javascript:src/config.js
export default {
  apiHost: 'https://api2.nicehash.com', // URLに注意してください
  apiKey: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  apiSecret: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  orgId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
}
```

`.env`にマイニングリグIDとLine Notifyのトークンを設定しておきます。
事前に用意したトークンを設定してください。

```bash:.env
RIG_ID=xxxxxxxxxx
LINE_TOKEN=xxxxxxxxxx
```

## ステータスを取得する

マイニングリグのスタータスを取得するファイルを作成します。

マイニングしていれば`MINING`、オフラインなら`OFFLINE`になります。その他にもステータスが複数がありますが公式ドキュメントを確認してください。今回はマイニングしてるか、それ以外のステータスで実装しています。

```javascript:src/rigStatus.js  
import config from './config'
import Api from './api'

const api = new Api(config);

const getStatus = async (rigId) => {
  return await api.getTime()
    .then(() => api.get(`/main/api/v2/mining/rig2/${rigId}`))
    .then(res => {
      return res.minerStatus;
    }).catch(err => {
      console.log('ERROR', err.error || err);
    });
}

module.exports = getStatus
```

```javascript:index.js
require = require("esm")(module);
require('dotenv').config();

const rigStatus = require("./src/rigStatus");

const rigID = process.env.RIG_ID;

const main = async () => {
  try {
    const res = await rigStatus(rigID);
    // undefinedのときにreturn
    if (!res) return;
    if (res === 'MINING') {
      console.log(res);
    } else if (res !== 'MINING') {
      console.log(res);
    }
  } catch (e) {
    console.log(e);
  }
}

main();
```

`src/rigStatus.js`と`index.js`が作成できたら実行してみます。
マイニングしていれば`MINING`と表示されます。

```bash:ステータス取得
$ node index.js 
MINING
```

## LINE Notifyで状態を通知

先ほど作成したステータスをLINEに通知するための`src/line.js`を作成します。

```javascript:src/line.js
const axios = require('axios');
const querystring = require('querystring');

const Line = function () { };

/**
 * LINE Notifyのトークンセット
 * @param {String} token LINE Notifyトークン
 */
Line.prototype.setToken = function (token) {
  this.token = token;
};

/**
 * LINE Notify実行
 * @param {String} text メッセージ
 */
Line.prototype.notify = function (text) {
  if (this.token == undefined || this.token == null) {
    console.error('undefined token.');
    return;
  }
  axios(
    {
      method: 'post',
      url: 'https://notify-api.line.me/api/notify',
      headers: {
        Authorization: `Bearer ${this.token}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      data: querystring.stringify({
        message: text,
      }),
    }
  )
    .then(function (res) {
      console.log(res.data);
    })
    .catch(function (err) {
      console.error(err);
    });
};

module.exports = Line;
```

## LINE通知テスト

`index.js`にLINE通知機能を追加します。

```javascript:index.js
require = require("esm")(module);
require('dotenv').config();

const rigStatus = require("./src/rigStatus");

// Lineモジュールを使用してインスタンスを作成
const Line = require('./src/line');
const myLine = new Line();
myLine.setToken(process.env.LINE_TOKEN);

const rigID = process.env.RIG_ID;

const main = async () => {
  try {
    const res = await rigStatus(rigID);
    // undefinedのときにreturn
    if (!res) return;
    if (res === 'MINING') {
      // LINEに通知する
      myLine.notify(res);
    } else if (res !== 'MINING') {
      // マイニングできていないときLINEに通知する
      myLine.notify(`※※※${res}※※※`);
    }
  } catch (e) {
    console.log(e);
  }
}

// 実行
main();
```

ターミナルから`index.js`を実行し、正しく動作すれば以下のようにconsole.logに200と`ok`が表示され、LINE Notifyトークンを発行した通知先にメッセージが届きます。

今回は`test`というトークンを発行したので、`[test]MINING`というメッセージになっています。

```bash:LINEテスト
$ node index.js 
{ status: 200, message: 'ok' }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901444/96fc4d34-aeaa-0e8e-3405-223652d6a0e6.png)

## node-cronで1分ごとにステータスチェック

ここまででマイニングリグのステータスチェックとLINEへの通知ができました。
しかし、通知をチェックするには毎回`node index.js`と実行する必要があり外出先でチェックすることができません。

そこで、`node-cron`というモジュールを使って一定期間ごとにステータスをチェックし通知します。

通知させる仕様として以下を定義します。

- 毎分チェックする
- リグステータスが`MINING`とそれ以外のステータス（マイニングできていない）の2通りをチェック
- 1分前のステータスから変化していれば通知する
- マイニングできていないステータスが60分続いていればもう一度通知する

`index.js`を実行したときに一定時間ごとにチェックするように追記します。

```javascript:src/index.js
require = require("esm")(module);
require('dotenv').config();

const cron = require('node-cron');
const rigStatus = require("./src/rigStatus");
const Line = require('./src/line');

const myLine = new Line();
myLine.setToken(process.env.LINE_TOKEN);

const rigID = process.env.RIG_ID;

// マイニング状態
let isMining = false;
// 停止した回数
let stopedMiningCount = 0;

const main = async () => {
  try {
    const res = await rigStatus(rigID);
    // undefinedのときにreturn
    if (!res) return;
    if (!isMining && res === 'MINING') {
      isMining = true;
      stopedMiningCount = 0;
      myLine.notify(res);
    } else if (res !== 'MINING') {
      // 60回チェック（60分マイニングができていないときに再通知）
      if (stopedMiningCount % 60 === 0) {
        myLine.notify(`※※※${res}※※※`);
      }
      isMining = false;
      stopedMiningCount++;
    }
  } catch (e) {
    console.log(e);
  }
}
// 指定した時間で監視通知（例では24時間チェックする）
cron.schedule('* * * * *', () => {
  main();
});
```

同じようにターミナルで実行し最初の通知が1分後にくれば完成です。

後はこのプロジェクトをサーバーなどで起動し、放置すればステータス監視の完成です。
私はこのアプリケーションをさくらのVPSサーバーで起動して放置しています。

```bash:LINEテスト
$ node index.js 
{ status: 200, message: 'ok' }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901444/96fc4d34-aeaa-0e8e-3405-223652d6a0e6.png)

# まとめ

Nicehashがサンプルを公開してくれていたので簡単に作成できました。
これのおかげでマイニングステータスを心配する必要がなくなったので精神的に余裕ができましたw

良いマイニングライフを！