【個人開発】GraphQL API を叩いて毎朝電気代が届くLine Botを開発する【前編】

## はじめに
今回、私が使用している **オクトパスエナジー** がGraphQL APIを提供しているので
それを用いて毎朝電気代が届くLine Botを開発します。

今回は、**Railsと外部API連携**、**GraphQLの叩き方**、 **Line Messaging API**を利用していきます。
定期実行、デプロイ編について後編記事で書く予定です(~~まだ実装してない~~)

そもそもGraphQLって何？という方は下記の記事をご覧ください

https://qiita.com/kim_t0814/items/c6ef7e3c668007f920a6

## 使用技術
Rails(APIモード): 7.0.8
Ruby: 3.2.0
graphql 2.0.27
Line Messaging API

## 実装方針
**1.Rails側からGraphQL叩いて電気代を取得出来るようにする
2.Line Messaging APIを利用して、取得した電気代情報をユーザーに送信する機能の実装　
-- 今回はここまで --
3.開発環境で定期実行用のタスクを作成する
4.本番環境で定期実行を行えるようにする**

## Rails側からGraphQL叩いて電気代を取得出来るようにする

オクトパスエナジーで電気代を取得するには以下の2ステップになります。

**1-1.emailとパスワードをクエリ変数として,認証トークンの取得(mutation)
1-2.認証トークンを使用して、電気代情報の取得(query)**

### MutationとQueryの使い分け
基本的には下記の図解の使い分けになります。

- RESTとは`Representational State Transfer`の略でwebサービスの設計モデルのこと
- SQLとはデータベース用のクエリ言語のこと
- GraphQLとはAPIのクエリ言語のこと

| | Rest | SQL | GraphQL
-- | -- | -- | --
データ取得 | GET | Select | Query
データ追加 | POST | Create | Mutation
データ更新 | PATCH | Update | Mutation
データ削除 | DELETE | Delete | Mutation

データの取得は**Query**でそれ以外は**Mutation**と考えると覚えやすいかも知れません(`因みにイベント監視はSubscription)`。

### 事前準備
とその前に RailsアプリでGraphqlが使えるように以下のgemを追加して`bundle install`を行います。

```ruby:Gemfile
gem 'graphql'
gem 'graphql-client'
gem 'dotenv-rails'
```

**`graphql-client`とは**
GraphQLのクエリを宣言、作成、実行を行うRubyライブラリことです。

詳しくは以下を参照してください。

https://github.com/github/graphql-client

**`dotenv-rails`とは**
環境変数を管理するRubyライブラリのことです。
自信が作成した、アプリケーションの直下に`.env`ファイルを作成することでシークレットキーやパスワードなど
ネットに公開させたくない情報を扱い、自動で読み込むことが可能です。

```ruby:.env
OCTOPUS_EMAIL = "XXX@example.com"
OCTOPUS_PASSWORD = "passwordhogefuga"
```

のような形式で定義することができます。
呼び出す際は `ENV["OCTOPUS_EMAIL"]` のような形式で呼び出すことができます！

隠したいファイルですので **.gitignoreを設定** することは忘れないようにしましょう。

詳しくは以下を参照してください。

https://github.com/bkeepers/dotenv

### 1-1.emailとパスワードをクエリ変数として,認証トークンの取得
上記インストールを行った後に、トークンの取得のために以下のファイルを作成します。

今回、`graphql-client`について補足説明が少なかったのでコメントアウトで
説明しておきます。

```lib/octopus_authentication_token.rb
require "graphql/client"
require "graphql/client/http"

class OctopusAuthenticationToken
  # http アダプターを設定
  HTTP = GraphQL::Client::HTTP.new("https://api.oejp-kraken.energy/v1/graphql/")

  # 上記を使用して、API サーバーから GraphQL Schema を取得
  Schema = GraphQL::Client.load_schema(HTTP)

  # 上記を使ってクライアント作成
  Client = GraphQL::Client.new(schema: Schema, execute: HTTP)

  # ログイントークンを作成する,mutationを定義
  LOGIN_QUERY = self::Client.parse <<~GRAPHQL
    mutation($input: ObtainJSONWebTokenInput!) {
      obtainKrakenToken(input: $input) {
        token
        refreshToken
      }
    }
  GRAPHQL

  def generate_token
    # mutationを実行してその実行結果をresultに格納する
    result = Client.query(LOGIN_QUERY, variables: {
      input: {
        email: ENV["OCTOPUS_EMAIL"],
        password: ENV["OCTOPUS_PASSWORD"],
      },
    })
    result.original_hash.dig("data", "obtainKrakenToken", "token")
  end
end
```

上記で、ログイントークンを取得するためのGraphQLクライアントの設定とクエリの定義と、
トークン作成用のメソッドを定義しております。


### 1-2.認証トークンを使用して、電気代情報の取得
さて、認証トークンを作成したと思うのでそのトークンを元に
電気代取得のためのHTTPクライアントを作成します。

```lib/octopus_client.rb
require "graphql/client"
require "graphql/client/http"

module OctopusClient    
  HTTP = GraphQL::Client::HTTP.new("https://api.oejp-kraken.energy/v1/graphql/") do
    def headers(context)
      { "Authorization" => "#{OctopusAuthenticationToken.new.generate_token}" }
    end
  end

  Schema = GraphQL::Client.load_schema(HTTP)
  Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end

```

上記では,`lib/octopus_authentication_token.rb`で作成した
`OctopusAuthenticationToken` クラスのインスタンスメソッドを読んで
tokenを取得しヘッダーにセットし、Client作成を行っています。

そして,`octopus_energy_bill/index`にアクセスをしたらAPIを叩くよう実装を行います。

```app/controllers/octopus_energy_bill_controller.rb
class OctopusEnergyBillController < ApplicationController

  GetBillQUERY = OctopusClient::Client.parse <<~'GRAPHQL'
	query(
		$accountNumber: String!
		$fromDatetime: DateTime
		$toDatetime: DateTime
	) {
		account(accountNumber: $accountNumber) {
			properties {
				electricitySupplyPoints {
					agreements {
						validFrom
					}
					halfHourlyReadings(
						fromDatetime: $fromDatetime
						toDatetime: $toDatetime
					) {
						startAt
            endAt
						value
						costEstimate
						consumptionStep
						consumptionRateBand
					}
				}
			}
		}
	}
  GRAPHQL

  def index 
    result = OctopusClient::Client.query(GetBillQUERY, variables: {
      accountNumber: ENV['OCTOPUS_ACCOUNT_NUMBER'],
      fromDatetime: Date.yesterday.beginning_of_day.iso8601,
      toDatetime: Date.yesterday.end_of_day.iso8601
      })
    properties = result.original_hash.dig("data", "account", "properties")
    electricity_supply_points = properties.first["electricitySupplyPoints"]
    half_hourly_readings = electricity_supply_points.first["halfHourlyReadings"]
    @kwh = half_hourly_readings.pluck("value").map(&:to_f).sum
    @cost = half_hourly_readings.pluck("costEstimate").map(&:to_f).sum
    puts "#{Date.yesterday.strftime('%Y年%m月%d日')}は#{@kwh.round(2)}kWh消費して#{@cost}円かかったよ" 
  end
end

```


```diff_ruby:routes.rb
+  resources :octopus_energy_bill, only: %i[index]
```




また、今回の実装にあたり`issue`を作成して実装したので参考にしたい方はどうぞ。

https://github.com/kimkim0814/octopus-energy-line-bot/issues/1

## Line Messaging APIを利用して、取得した電気代情報をユーザーに送信する機能の実装

下記のgemを前の章同様にinstallしていきます。
```ruby:Gemfile
gem 'line-bot-api'
```

https://github.com/line/line-bot-sdk-ruby

Messaging API　チャンネルを作成していきます。

https://qiita.com/KNR109/items/e1b5ebd94393441fff74#messaging-api-%E3%81%AE%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90

上記の記事で詳しく説明されているので、当記事では省略します。

![スクリーンショット 2023-10-17 1.10.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/2e67195a-60a4-9ecb-824f-64f91a42d3e4.png)

上記のような形で、今回はオクトパスエナジーの非公式botを作成しました。



また、今回作成された `チャネルシークレト`を`LINE_CHANNEL_SECRET`に、`チャンネルアクセストークン`を`LINE_CHANNEL_TOKEN`に`.env`ファイルで定義します。

さて、`チャネルシークレット`と`チャネルアクセストークン`を使用してクライアントを作成します。
```lib/line_bot_client.rb
class LineBotClient
  def client 
    Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
    }
  end
end

```
この記述によって`LineBotClient.new.client`、`Line::Bot::Client`のインスタンスを作成出来るようになります。

さて、上記を使ってLINEを送ってみましょう！！

```diff_ruby:app/controllers/octopus_energy_bill_controller.rb
class OctopusEnergyBillController < ApplicationController

	GetBillQUERY = OctopusClient::Client.parse <<~'GRAPHQL'
		query(
			$accountNumber: String!
			$fromDatetime: DateTime
			$toDatetime: DateTime
		) {
			account(accountNumber: $accountNumber) {
				properties {
					electricitySupplyPoints {
						agreements {
							validFrom
						}
						halfHourlyReadings(
							fromDatetime: $fromDatetime
							toDatetime: $toDatetime
						) {
							startAt
							endAt
							value
							costEstimate
							consumptionStep
							consumptionRateBand
						}
					}
				}
			}
		}
  GRAPHQL

  def index 
    result = OctopusClient::Client.query(GetBillQUERY, variables: {
      accountNumber: ENV['OCTOPUS_ACCOUNT_NUMBER'],
      fromDatetime: Date.yesterday.beginning_of_day.iso8601,
      toDatetime: Date.yesterday.end_of_day.iso8601
      })

    properties = result.original_hash.dig("data", "account", "properties")
    electricity_supply_points = properties.first["electricitySupplyPoints"]
    half_hourly_readings = electricity_supply_points.first["halfHourlyReadings"]
    @kwh = half_hourly_readings.pluck("value").map(&:to_f).sum
    @cost = half_hourly_readings.pluck("costEstimate").map(&:to_f).sum
-   puts "#{Date.yesterday.strftime('%Y年%m月%d日')}は#{@kwh.round(2)}kWh消費して#{@cost}円かかったよ"
+   text = "#{Date.yesterday.strftime('%Y年%m月%d日')}は#{@kwh.round(2)}kWh消費して#{@cost}円かかったよ" 

+	message = {
+     type: 'text',
+     text: text
+    }
+    LineBotClient.new.client.broadcast(message)
  end
end

```

そして、`/octopus_energy_bill`エンドポイントにアクセスすると、以下のようにLINEが送ることができます。

![IMG_9781.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/07f3e7ef-f92a-0694-0e02-6174757e7dc2.png)

以上で今回の目標物は完成です！！

## 最後に
いかがでしたでしょうか。
今回は、GraphQLとRails(APIモード)を使用して、電気代Line botを開発していきました。

次回はデプロイと定期実行を行なっていこうと思います🔥

最後まで読んで頂きありがとうございました！

### 後編記事

https://qiita.com/kim_t0814/items/b5f881624633accd84a6

## 参考文献
https://qiita.com/megane42/items/30fed8d038a1ee71ce0d

https://qiita.com/KNR109/items/e1b5ebd94393441fff74

https://developer.octopus.energy/docs/api/