🍇

実践形式で学ぶ! Go言語で作成するdiscordbot 1章: 基礎編

に公開

はじめに

本記事では、以下の流れでdicordbotをGo言語で作成する方法について紹介します。

  1. Botユーザーの作成
  2. サーバーへの招待
  3. 最小実装(オンラインにするだけ)
  4. オウム返しBot
  5. 小規模でも保守しやすいフォルダ分割
  6. トラブルシューティング

この記事で作成するプログラムは下記githubリポジトリで公開しています。

https://github.com/orchidee9392/discordbot-tutorial

前提条件

  • Discordアカウントを所持していること
  • Botを招待できるDiscordサーバーがあること
  • Go 1.25以降

1. Botユーザーの作成

  1. My Applications | Discord Developer Portalにアクセスする。

  2. 右上の「New Aplication」をクリックする。

  3. 名前を入力してポリシーに同意したら「Create」ボタンを押下する。

  4. 左サイドバーのから「Bot」を開き、「Reset Token」を押下する。
    初回は「Reset Token」で新規発行の扱いになります。

  5. Yes, do it!

  6. discordのパスワードを入力する(他の方法で認証してもOK)。

  7. 「Copy」を押下して、パスワードを控える。

トークンは機密情報です。公開リポジトリにコミットしたり、第三者へ共有したりしないでください。

  1. 「Bot」タブのまま、ページの下の方に行き、「Message Content Intent」を有効にする。

  2. 「Save Changes」を押下して設定を保存する。

2. サーバーへの招待

  1. 左サイドバーから「OAuth2」タブを開き、OAuth2 URL Generatorセクションまで移動する。

  2. ScopesのBotにチェックをつける。

  3. Bot Permissionsは以下のみチェックをつける(最小権限)。

  • Send Message
  • Read Message History
  1. ページ最下部の「Generated URL」にアクセスして招待したいサーバーにbotを招待する。

  2. 用意したサーバーにbotが参加したら完了です。

3. 最小実装(オンラインにするだけ)

  1. goモジュールの初期化
go mod init github.com/orchidee9392/discordbot-test
go get github.com/bwmarrin/discordgo@latest

goモジュール名のgithub.com/orchidee9392/discordbot-testは自身のリポジトリに合わせて変更してください。

  1. 実装
    main.go1ファイルにすべて実装します。
main.go
package main

import(
    "log"
    "os"
    "github.com/bwmarrin/discordgo"
)

func main(){
    //トークンの取得
    token := os.Getenv("DISCORD_TOKEN")
    if token == ""{
        log.Fatal("DISCORD_TOKEN が未設定です")
    }

    //Discordセッションの生成
    session, err := discordgo.New("Bot " + token)
    if err != nil{
        log.Fatal(err)
    }
  
    //Intentsの設定
    session.Identify.Intents = discordgo.IntentsGuilds

    //セッション開始
    if err := session.Open(); err != nil{
        log.Fatal(err)
    }
    defer session.Close()

    log.Println("botが接続されました。")
    fmt.Println("Enterで終了...")
    fmt.Scanln()
}
  1. 起動
  • macOS / Linux(ターミナル)
export DISCORD_TOKEN="ここにBotトークン"
go run .
  • Windows(PowerShell)
$env:DISCORD_TOKEN="ここにBotトークン"
go run .

サーバーのメンバー一覧で Botがオンライン になっていれば成功です。
Enterで終了します。

4. オウム返しBotの作成

オウム返しBotもmain.go1ファイルで実装してみます。

main.go
package main

import (
    "fmt"
    "log"
    "os"

    "github.com/bwmarrin/discordgo"
)

func main() {
    //トークンの取得
    token := os.Getenv("DISCORD_TOKEN")
    if token == "" {
        log.Fatal("DISCORD_TOKEN が未設定です")
    }

    //Discordセッションの生成
    session, err := discordgo.New("Bot " + token)
    if err != nil {
        log.Fatal(err)
    }

    //Intentsの設定
    session.Identify.Intents = discordgo.IntentsGuilds |
        discordgo.IntentsGuildMessages |
        discordgo.IntentsMessageContent

    //メッセージ受信ハンドラを登録
    session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
        // Botの発言には反応しない(無限ループ防止)
        if m.Author.Bot {
            return
        }

        _, _ = s.ChannelMessageSend(m.ChannelID, m.Content)
    })

    //セッション開始
    if err := session.Open(); err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    log.Println("botが接続されました。")
    fmt.Println("Enterで終了...")
    fmt.Scanln()

}

メッセージ内容を受信・送信するには、Developer Portal側でMessage Content Intentを有効にし(手順1-8)、コード側でもIntentとしてIntentsMessageContentを追加する必要があります。

5. 小規模でも保守しやすいフォルダ分割

規模が大きくなくても、責務毎にファイルを分けておくと長期運用が容易になります。
正解はありませんが、以下に分割例を記します。

.
├── README.md
├── cmd
│   └── sample
│       └── main.go
├── go.mod
├── go.sum
└── internal
    ├── app
    │   └── bot.go
    ├── config
    │   └── config.go
    └── handlers
        └── message_create.go

internal/config/config.go
設定を表すクラスです。

config.go
package config

import (
	"fmt"
	"os"
)

//設定構造体
type Config struct {
	Token string
}

//環境変数を読み込んで設定structを作成します
func FromEnv() (Config, error){
	t := os.Getenv("DISCORD_TOKEN")
	if t == "" {
		return Config{}, fmt.Errorf("DISCORD_TOKEN が設定されていません。")
	}

	return Config{Token: t}, nil
}

internal/app/bot.go
discordbotを表すクラスです。

bot.go
package app

import "github.com/bwmarrin/discordgo"

// ボット構造体
type Bot struct {
	session *discordgo.Session
}

// 初期化関数
func New(token string, intents discordgo.Intent) (*Bot, error) {
	s, err := discordgo.New("Bot " + token)
	if err != nil {
		return nil, err
	}

	//Intents未指定なら最小のIntentsを与える
	if intents == 0 {
		intents = discordgo.IntentsGuilds
	}
	s.Identify.Intents = intents

	return &Bot{session: s}, nil
}

// 接続開始
func (b *Bot) Start() error {
	return b.session.Open()
}

// 接続終了
func (b *Bot) Stop() {
	_ = b.session.Close()
}

// メッセージ作成イベントのハンドラを登録
func (b *Bot) OnMessageCreate(h func(*discordgo.Session, *discordgo.MessageCreate)) func() {
	return b.session.AddHandler(h)
}

internal/handlers/message_create.go
メッセージ作成のイベントハンドラを表すクラスです。

message_create.go
package handlers

import "github.com/bwmarrin/discordgo"

//オウム返しするイベント
func Echo(s *discordgo.Session, m *discordgo.MessageCreate) {
	if m.Author.Bot {
		return
	}

	_, _ = s.ChannelMessageSend(m.ChannelID, m.Content)
}

cmd/sample/main.go
メインクラスです。

main.go
package main

import (
	"fmt"
	"log"

	"github.com/bwmarrin/discordgo"
	"github.com/orchidee9392/discordbot-tutorial/internal/app"
	"github.com/orchidee9392/discordbot-tutorial/internal/config"
	"github.com/orchidee9392/discordbot-tutorial/internal/handlers"
)

func main() {
	//設定ロード
	cfg, err := config.FromEnv()
	if err != nil {
		log.Fatal(err)
	}

	//使用イベント設定
	intents := discordgo.IntentsGuilds |
	 	discordgo.IntentsGuildMessages |
	  	discordgo.IntentsMessageContent

	//Bot生成
	bot, err := app.New(cfg.Token, intents)
	if err != nil {
		log.Fatal(err)
	}

	//ハンドラ登録
	removeEcho := bot.OnMessageCreate(handlers.Echo)
	defer removeEcho()

	//接続
	if err := bot.Start(); err != nil {
		log.Fatal(err)
	}
	defer bot.Stop()

	log.Println("接続完了")
	fmt.Println("Enterで終了...")
	fmt.Scanln()
}

以上で、discordbotの作成から最小動作、簡単なメッセージ処理、フォルダ分割までを紹介しました。
次章以降では、スラッシュコマンドなどについて紹介していきたいと思います。

6. トラブルシューティング

症状 主な原因 対処
オンラインにならない トークンが間違っている/ネットワークがつながっていない トークンの確認、ファイアウォール・プロキシ確認
Botがメッセージを受け取っていない Message Content Intent 未有効(Portal側 or コード側) Portalで有効化 → Save、コードで IntentsMessageContent を追加
Botからメッセージを送信できない チャンネル権限不足 サーバー側で Bot のロールに View/Read/Send を付与

Discussion