Go言語で作成した利用者専用のDiscord Bot スクショ送信Bot

## 作ったもの
私、スマホゲーマーなんです。
そして、PCゲーマーでもあります。
という事は、PCに入れたNox等で複数キャラ同時育成なんかやっちゃいます。

私のPCこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3764841/41128204-ad23-3f0c-343a-442a59ff9abc.png)

外出時等、アプリが落ちてしまったりしていないか
キャラが死んでいないかいつもドキドキしています。

そのチェックをするために今回開発を行ったのが
DiscordのBotを利用して、Discordで話しかけるたびに
デスクトップのスクショを送ってくれるソフトを開発しました！！！

![discord.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3764841/5c32de5b-39f8-1ae8-46bb-ce4dd77abd13.png)


このBotに話かけるだけで
なんとまぁ素敵。PCのゲーム実行状態が一目でわかります。

プログラム自体はこちらで公開しております。
※Windows専用かもしれません。
https://github.com/my-take-dev/discord-bot-screenshot-sender

build済アプリをこちらからダウンロードすることも可能です。
https://github.com/my-take-dev/discord-bot-screenshot-sender/releases/tag/v1.0.0

但し、もろもろセキュリティにひっかかる為・・・・・
開発用PCからゲーム用PCにアプリ移動するのすら大変でした。

Go言語の開発環境をネット記事等読んで作成していただき
自分でexeにしたほうがらくかもしれません。

## discordについて
Discordでは誰でも簡単にBotを作成する事ができ
それを自分のチャンネル専用にする事ができます。

DiscordのBotは、
チャットルームに存在するBotとしての存在と
Botと常に通信を行って実際に処理を行っているアプリが存在しており
私が作成したのはBotの裏側となる部分です。

開発物以外の説明に関しては
世に資料が多々存在しますので、省略させていただきます。

# Discord Bot開発について
今回は単純にDiscordのBotと接続し、会話をトリガーとして
デスクトップのスクリーンショットを、一旦保管して送信するといった
プログラム的に見てもあまり面白いものでもありません。

どちらかというと単純な2動作をセットにしたアイデア開発かな？と思っています。

DiscordのBotについては、もう少し本格的なものを作って遊びましたので
次回は、そちらを紹介していこうと思います。

```go 
package main

import (
	"fmt"
	"os"

	"image/jpeg"

	"github.com/bwmarrin/discordgo"
	"gopkg.in/ini.v1"

	"github.com/kbinani/screenshot"
)

func main() {

	// アプリエラー発生時に、画面が閉じてしまうと原因がわからないので
	// 画面に表示させるために利用したり
	// Discordからの通信待ちをするのに利用します。
	stopProgram := make(chan bool)

	// iniファイルからDiscordeのBOT設定値を読み込む
	discordSetting, err := loadSetting()
	if err != nil {
		fmt.Println(err)
		fmt.Println("設定ファイルからTokenが取得できませんでした。")
		fmt.Println("設定ファイルをご確認の上、この画面を×で閉じてから再度実行してください。")
		<-stopProgram
	}

	//Discordのセッションを作成
	discord, err := discordgo.New()
	if err != nil {
		fmt.Println(err)
		fmt.Println("Discordのセッションを作成できませんでした。")
		fmt.Println("ここでは特に何もしていないはずなので、エラーの原因がわかりませんでした。")
		fmt.Println("設定ファイル等をご確認の上、この画面を×で閉じてから再度実行してください。")
		<-stopProgram
	}

	// トークンを設定
	discord.Token = discordSetting.Token()

	// Discordeから受信したイベントを受け取る
	// ここでいうイベントとは会話
	discord.AddHandler(onMessageCreate)

	// discordeのBotとの通信を開始
	err = discord.Open()
	if err != nil {
		fmt.Println(err)
		fmt.Println("DiscordのBotとの通信を開始できませんでした。")
		fmt.Println("Tokenの設定、Bot側の設定を間違えている可能性があります")
		fmt.Println("設定状況をご確認の上、この画面を×で閉じてから再度実行してください。")
		<-stopProgram
	}

	// アプリ終了時にdiscordeのBotとの通信を終了させる処理
	defer discord.Close()

	fmt.Println("プログラムスタート！")

	//プログラムが終了しないようロック
	// stopBotに通信が来るまでここで待機という書き方。
	// 送信ロジックを作っていないので、一生勝手には進まない。

	<-stopProgram

}

// Discordから受信したメッセージを処理します。
func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	// しゃべったのがbotの場合には処理をしません。
	if m.Author.Bot {
		return
	}

	// スクリーンショットを撮って画像ファイルに保存
	err := createNewScreenshotJpeg()
	if err != nil {
		fmt.Println("スクリーンショットの画像保存に失敗しました。")
		return
	}
	//Discord側に画像を送信
	sendfile(s, m.ChannelID, "out.png") //画像送信対応

}

// ///////////////////
// 画像送信
// ///////////////////
func sendfile(s *discordgo.Session, channelID string, jpegName string) error {

	screenshotJpeg, err := os.Open("out.png")
	if err != nil {
		return fmt.Errorf("送信する画像が見当たりません。err:%v", err.Error())
	}
	defer screenshotJpeg.Close()
	_, err = s.ChannelFileSend(channelID, jpegName, screenshotJpeg)

	if err != nil {
		return fmt.Errorf("画像をDiscordeに送信したけど、エラーが返却されたよ err:%v", err.Error())
	}
	return nil
}

// ///////////////////
// スクショand保存
// ///////////////////
func createNewScreenshotJpeg() error {
	bounds := screenshot.GetDisplayBounds(0)
	img, _ := screenshot.CaptureRect(bounds)
	file, err := os.Create("out.png")
	if err != nil {
		return fmt.Errorf("スクリーンショットの画像保存に失敗しました。 err:%v", err.Error())
	}
	defer file.Close()
	jpeg.Encode(file, img, &jpeg.Options{Quality: 80})

	return nil
}

// ///////////////////
// ini からToken情報を取得してくる処理
// ///////////////////
type DiscordSetting struct {
	tokenId string
}

func newDiscordSetting(token string) DiscordSetting {
	return DiscordSetting{token}
}

func (d DiscordSetting) Token() string {
	return d.tokenId
}

func loadSetting() (DiscordSetting, error) {
	// ファイル読み込み
	cfg, err := ini.Load("discordSetting.ini")
	if err != nil {

		return DiscordSetting{}, fmt.Errorf("設定ファイル(discordSetting.ini)が、見当たりません。 err:%v", err)
	}

	// iniからtokenidを取得
	tokenId := cfg.Section("bot").Key("token").String()

	_tokenId := "Bot " + tokenId

	return newDiscordSetting(_tokenId), nil
}

////////////////////////

```

サンプルiniファイル
```ini
[bot]
token=XXXXXXXXXXXXXXXXXXXXX // トークン
```