1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ラズパイサーバーでdiscord botを常時稼働させる

1
Last updated at Posted at 2025-12-02

合同会社Asmiraiでは、業務委託メンバーの日報(作業ログ)をDiscordで送ってもらう運用をしています。
このログ、使い方次第では案件の稼働時間管理や、今後の見積もり精度アップに使える宝の山では???

ただ GCP や AWS に常時課金するほどでもない…

ということで埃かぶってた Raspberry Pi Zero 2 W を引っ張り出してサーバー化しました!
microSDカードリーダー なども使用します。

この記事では サービスアカウントの秘密鍵(JSON) を使って Google Sheets にアクセスします。
・鍵ファイルは GitHub などに絶対コミットしない
・権限は最小限(スプレッドシート編集だけ)
・必要に応じて IP 制限やローテーション
など、運用面のセキュリティは各自の判断でお願いします。

概要

arch

転記されるスプシの様子

データは仮です。
sp

目的

案件ごとの稼働時間集計や見積もりのデータをいつか活用したいので、Googleスプレッドシートに蓄積する

この記事で説明すること

  • Discord Bot 作成
  • Raspberry Pi セットアップ
  • Google Sheets API 認証(サービスアカウントJSON)
  • Python + discord.py 実装
  • systemd 自動起動

手順1: Discord botの作成

  1. 下記にアクセスして New Application を押して Create します

    https://discord.com/developers/applications

  2. 適当なアイコンを設定しましょう

  3. 左の OAuth2 → URL Generator で、Scopes は bot にチェック、
    Bot Permissions は View Channels Send Messages Read Message History Add Reactions を選択します
    discordbotpermission.png

  4. 画面下で Generated URL が作成されるので、アクセスして Discord サーバーに bot を追加します。

  5. 左の Bot メニューから Message Content Intent を ON にして Save Changes を押します
    bot1


手順2: Raspberry Pi の初期設定

下記のリンクから Imager 書き込み用のソフトをインストールします
https://www.raspberrypi.com/software/

  1. Device は今回使用する Zero 2 W を指定
  2. OS は Raspberry Pi OS Lite 64bit を選択
  3. Hostname や User名、Wi-Fi 等の設定を行います。この時 SSH の設定を有効化 してください。

手順3: Raspberry PiにSSH接続

  1. ターミナルで以下のコマンドを実行します
ssh ユーザー名@ホスト名.local

ユーザー名とホスト名は先ほど設定したものに置き換えてください。

お使いのPCのWi-Fiは先ほど設定したものと同じものに接続します


手順4: zram(スワップ)を増やす

効果があるかわかりませんが、メモリ不足クラッシュ対策に増やしておきます。

  1. 設定ディレクトリの作成
sudo mkdir -p /etc/systemd/zram-generator.conf.d
  1. override.conf の作成
sudo nano /etc/systemd/zram-generator.conf.d/override.conf
  1. 編集
[zram0]
zram-size = 1024
compression-algorithm = zstd

保存(Ctrl+O → Enter)
終了(Ctrl+X)

  1. 再起動
sudo reboot
  1. 確認
swapon --show

SIZE1024M になっていればOKです。


手順5: Google Sheets API と Google Drive API の有効化

ラズパイ側からスプシを編集するための API 有効化を行います。

  1. 下記にアクセスし、新規プロジェクトを作成
    https://console.cloud.google.com/welcome

  2. 左のメニューから 「有効なAPIとサービス」 をクリックし、
    「APIとサービスを有効にする」をクリックします
    gcp1

  3. Google Sheets API を有効化します

  4. 同様の手順で Google Drive API も有効化します


手順6: サービスアカウントと鍵ファイルの作成

6-1. サービスアカウント作成

  1. GCP コンソールで
    「IAM と管理」 → 「サービスアカウント」 を開く

  2. 「サービスアカウントを作成」から

    • 名前: raspi-worklog-bot
    • ロールは後から付けるので一旦スキップ or 最小限でOK

作成されるメールアドレスの例:

raspi-worklog-bot@discord-bot-XXXXX.iam.gserviceaccount.com

このメールアドレスは後で使うのでメモしておきます。

6-2. サービスアカウントキー(JSON)の作成

  1. 作成したサービスアカウントを選択
  2. 「鍵」タブ → 「鍵を追加」 → 「新しい鍵を作成」
  3. 種類: JSON を選んでダウンロード

discordbot-sa.json などの名前にして保管しておきます。

この JSON は 超重要な秘密鍵 なので、
GitHub や共有ストレージに上げない・メール添付しない・バラ撒かないよう注意してください。

6-3. スプレッドシートの用意&共有

  1. Google スプレッドシートで、以下のようなカラムを持つシートを作成します(シート名は「作業ログ」など)

    sps1

  2. 右上の 共有 ボタンを押し、
    先ほどのサービスアカウントのメールアドレスを「編集者」として追加します。


手順7: ラズパイに gcloud と鍵ファイルを配置

7-1. gcloud のインストール

ラズパイに SSH で接続した状態で実行。

sudo apt-get install -y apt-transport-https ca-certificates gnupg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
 | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
sudo mkdir -p /usr/share/keyrings
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
 | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
sudo apt-get update
sudo apt-get install -y google-cloud-cli
gcloud version

gcloud init は今回は必須ではありませんが、プロジェクト選択などしたい場合は実行しておいてもOKです)

7-2. サービスアカウントキーをラズパイへコピー

Mac からラズパイに discordbot-sa.json をコピーする例(scp):

scp discordbot-sa.json ユーザー名@ホスト名.local:/home/ユーザー名/discord-bot/discordbot-sa.json

ラズパイ側でファイルがあることを確認:

ls /home/ユーザー名/discord-bot/discordbot-sa.json

手順8: Python 環境構築と動作確認

8-1. ディレクトリ作成 & venv

ラズパイ上で:

mkdir -p ~/discord-bot
cd ~/discord-bot
python -m venv venv
source venv/bin/activate
pip install gspread google-auth google-auth-httplib2 google-auth-oauthlib discord.py

8-2. test_sheets.py で単体テスト

nano test_sheets.py

中身:

from google.oauth2 import service_account
import gspread

SCOPES = [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive",
]

SERVICE_ACCOUNT_FILE = "discordbot-sa.json"  # 同じディレクトリに置いた場合

def get_sheet():
    creds = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE,
        scopes=SCOPES,
    )
    gc = gspread.authorize(creds)
    return gc.open("作業ログ").sheet1   # あなたのスプレッドシート名に合わせて変更

def main():
    sheet = get_sheet()
    sheet.append_row(["テスト案件", "user", "接続テスト", 0.1, "SAで書き込めた"])
    print("書き込みOK")

if __name__ == "__main__":
    main()

実行:

python test_sheets.py

書き込みOK と表示され、スプレッドシートに1行追記されていれば成功です。


手順9: bot の実装

Discord サーバーに、チャンネルカテゴリ「受託案件」配下に案件ごとのチャンネルがある前提の実装です。
必要に応じてカテゴリ名など修正してください。

9-1. bot.py の作成

cd ~/discord-bot
nano bot.py

中身(トークンは必ず置き換える&本当は環境変数推奨):

import re

import discord
from google.oauth2 import service_account
import gspread

# ==== 設定ここだけ変える ====
DISCORD_TOKEN = "YOUR_DISCORD_BOT_TOKEN"  # 実際のトークンは.envなどで管理推奨
SPREADSHEET_NAME = "作業ログ"              # スプレッドシート名
TARGET_CATEGORY_NAME = "受託案件"          # 監視するカテゴリ名
SERVICE_ACCOUNT_FILE = "discordbot-sa.json"  # サービスアカウントJSONのパス
# ==========================

SCOPES = [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive",
]


def get_sheet():
    """サービスアカウントで Google Sheets のシートを返す"""
    creds = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE,
        scopes=SCOPES,
    )
    gc = gspread.authorize(creds)
    return gc.open(SPREADSHEET_NAME).sheet1


def parse_worklog(content: str, author_name: str, project_name: str):
    """
    作業ログメッセージをパースして、スプレッドシートに書き込む用の行リストを返す。

    想定フォーマット:
    [作業ログ]
    作業内容:今後の方針MTG
    稼働時間 : 0.25
    メモ:aaa

    作業内容:API設計
    稼働時間(時間): 2.5
    メモ: 楽しかった
    """

    lines = content.splitlines()

    # 先頭が [作業ログ] でなければ無視
    if not lines or not lines[0].startswith("[作業ログ]"):
        return []

    # 全角/半角コロン・スペースに緩く対応
    task_re = re.compile(r"^作業内容\s*[::]\s*(.+)$")
    hours_re = re.compile(r"^稼働時間.*[::]\s*(.+)$")  # 稼働時間(時間): も拾う
    memo_re = re.compile(r"^メモ\s*[::]\s*(.+)$")

    entries = []
    current = {}

    for line in lines[1:]:
        line = line.strip()
        if not line:
            continue

        m_task = task_re.match(line)
        if m_task:
            # もし前の作業が残っていたら確定
            if "作業内容" in current:
                entries.append(current)
                current = {}
            current["作業内容"] = m_task.group(1)
            continue

        m_hours = hours_re.match(line)
        if m_hours:
            value = m_hours.group(1)
            # 「0.25時間」みたいな書き方にも対応
            value = value.replace("時間", "").strip()
            current["稼働時間"] = value
            continue

        m_memo = memo_re.match(line)
        if m_memo:
            current["メモ"] = m_memo.group(1)
            continue

    # 最後の1件を追加
    if current.get("作業内容"):
        entries.append(current)

    rows = []
    for e in entries:
        try:
            hours = float(e.get("稼働時間", "0"))
        except ValueError:
            hours = 0.0

        rows.append([
            project_name,          # 案件名 = チャンネル名
            author_name,           # 作業者 = 投稿者名
            e.get("作業内容", ""), # 作業内容
            hours,                 # 稼働時間(時間)
            e.get("メモ", ""),     # メモ
        ])

    return rows


# ==== Discord クライアント ====
intents = discord.Intents.default()
intents.message_content = True  # Developer Portal で Message Content Intent を ON にしておく
client = discord.Client(intents=intents)


@client.event
async def on_ready():
    print(f"Logged in as {client.user.name} ({client.user.id})")


@client.event
async def on_message(message: discord.Message):
    # 自分自身には反応しない
    if message.author == client.user:
        return

    # カテゴリが「受託案件」以外のチャンネルは無視
    category = getattr(message.channel, "category", None)
    if category is None or category.name != TARGET_CATEGORY_NAME:
        return

    # 作業ログ以外は無視
    if not message.content.startswith("[作業ログ]"):
        return

    # 投稿者名(ニックネーム優先)
    author_name = message.author.display_name
    # 案件名 = チャンネル名
    project_name = message.channel.name

    # パース
    rows = parse_worklog(message.content, author_name, project_name)
    if not rows:
        await message.channel.send("作業ログの形式がパースできませんでした…")
        return

    # Sheets に書き込み
    try:
        sheet = get_sheet()
        for row in rows:
            sheet.append_row(row)

        await message.add_reaction("")
        await message.channel.send(
            f"{len(rows)}件の作業ログをスプレッドシートに保存しました。"
        )

    except Exception as e:
        await message.channel.send(
            f"スプレッドシート書き込みでエラーが発生しました: {e}"
        )


# 起動
client.run(DISCORD_TOKEN)

9-2. bot.py の実行テスト

python bot.py

コンソールに

Logged in as {bot名} (ID)

のように出たら、Discord サーバーの 「受託案件」カテゴリ配下のチャンネルで以下のように送信してみます。

[作業ログ]
作業内容:バグ修正1
稼働時間 : 0.25
メモ:わーい

作業内容:バグ修正2
稼働時間 : 0.25
メモ:わーい2

✅ リアクションと「2件の作業ログをスプレッドシートに保存しました。」のメッセージが返り、スプレッドシートに 2 行追加されていれば OK です。


手順10: systemd 化(自動起動)

ラズパイ起動時に自動で Bot が立ち上がるようにします。

10-1. systemd のサービスファイル作成

sudo nano /etc/systemd/system/worklog-bot.service

中身:{user名} はご自身のユーザーに変更してください。

[Unit]
Description=Discord Worklog Bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User={user名}
WorkingDirectory=/home/{user名}/discord-bot
ExecStart=/home/{user名}/discord-bot/venv/bin/python /home/{user名}/discord-bot/bot.py
Restart=always
RestartSec=5
Environment="PYTHONUNBUFFERED=1"

[Install]
WantedBy=multi-user.target

10-2. 設定の再読み込みと自動起動の有効化

# 設定ファイル(.service)の再読み込み
sudo systemctl daemon-reload

# worklog-bot サービスを新しい状態で再起動
sudo systemctl restart worklog-bot

# サービスの状態を確認する
sudo systemctl status worklog-bot

# ラズパイ起動時の自動起動を有効化
sudo systemctl enable worklog-bot

active (running) になっていれば成功です。

完成!!

お疲れ様でした!
以上で完成です!あとはラズパイを適当な USB ポートから電源供給しておけば、
Discord に投げられた [作業ログ] が自動で Google スプレッドシートに溜まっていく小さな「社内専用サーバー」 の出来上がりです。

剥き出しで放置されるサーバー()...
rasp_server.jpg

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?