はじめに
こんにちは!今回は、FirebaseとQiita API、そしてdiscord.pyを組み合わせて、指定したメンバーのQiita記事をDiscord上で通知してくれるBotを作成してみました。
このBotは以下の機能を持っています:
- メンバーの追加・削除
- 登録されたメンバー一覧の表示
- 今日投稿された記事の通知
- 今週投稿された記事の通知
それでは、実際のコードとともに作り方を解説していきます。
環境設定
必要なライブラリのインストール
まずは、必要なライブラリをインストールします。
pip3 install discord.py firebase-admin python-dotenv requests
Firebaseの設定
-
Firebaseプロジェクトの作成
Firebase Consoleで新しいプロジェクトを作成します。
-
サービスアカウントの作成
プロジェクトの設定からサービスアカウントを選択し、新しい秘密鍵を生成してダウンロードします。このファイルをプロジェクトディレクトリに
firebase.json
として保存します。 -
Firestoreの有効化
データベースとしてFirestoreを使用します。Firestoreを有効化し、セキュリティルールを適切に設定します。
Discord Botの作成
-
Discord Developer Portalでアプリケーションを作成
Discord Developer Portalで新しいアプリケーションを作成します。
-
Botの追加
アプリケーションにBotを追加し、Botのトークンを取得します。このトークンは後で使用します。
-
必要なIntentの有効化
Botの設定で「MESSAGE CONTENT INTENT」を有効にします。
-
Botをサーバーに追加
OAuth2 URL Generatorを使用して、Botを自分のDiscordサーバーに招待します。
.env
ファイルの作成
プロジェクトディレクトリに.env
ファイルを作成し、Discord Botのトークンを設定します。
DISCORD_BOT_TOKEN=your_discord_bot_token_here
コードの解説
では、実際のコードを見ていきましょう。
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
import firebase_admin
from firebase_admin import credentials, firestore
import asyncio
import requests
from typing import List, Dict, Any
from datetime import date, timedelta
# .env ファイルから環境変数を読み込む
load_dotenv()
# Discord Botの設定
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
# Firebaseの初期化
cred = credentials.Certificate("./firebase.json")
firebase_admin.initialize_app(cred)
db = firestore.client()
# Firestoreから登録された名前を取得
async def get_names_from_firestore() -> List[str]:
names_ref = db.collection('names')
docs = names_ref.stream()
return [doc.to_dict()['name'] for doc in docs]
# 登録されている名前一覧を表示
async def display_names_list(ctx: commands.Context) -> None:
try:
names = await get_names_from_firestore()
if names:
names.sort()
names_list = "\n".join(names)
await ctx.send(f"登録されている名前一覧:\n```\n{names_list}\n```")
else:
await ctx.send("登録されている名前はありません。")
except Exception as e:
print(f"Error in displaying names: {str(e)}")
await ctx.send(f"名前の表示中にエラーが発生しました: {str(e)}")
# Qiita APIを使用して記事を取得
def get_articles(userid: str) -> List[Dict[str, Any]]:
url = f'https://qiita.com/api/v2/users/{userid}/items'
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"APIリクエストエラー: {e}")
return []
# 名前の入力を処理
async def handle_name_input(ctx: commands.Context, action: str) -> str:
await ctx.send(f"{action}したい名前を入力してください")
def check(m):
return m.author == ctx.author and m.channel == ctx.channel
try:
user_message = await bot.wait_for('message', check=check, timeout=30.0)
return user_message.content.strip()
except asyncio.TimeoutError:
await ctx.send("タイムアウトしました。もう一度お試しください。")
return ""
# 名前を追加するコマンド
@bot.command(name='add')
async def add_name(ctx: commands.Context) -> None:
await display_names_list(ctx)
name = await handle_name_input(ctx, "追加")
if not name:
return
try:
names = await get_names_from_firestore()
if name not in names:
db.collection('names').add({'name': name})
await ctx.send(f'{name} を追加しました')
else:
await ctx.send(f'{name} は既に登録されています')
except Exception as e:
print(f"Error in add command: {str(e)}")
await ctx.send(f"エラーが発生しました: {str(e)}")
# 登録されている名前一覧を表示するコマンド
@bot.command(name='ls')
async def display_names(ctx: commands.Context) -> None:
await display_names_list(ctx)
# 名前を削除するコマンド
@bot.command(name='rm')
async def delete_name(ctx: commands.Context) -> None:
await display_names_list(ctx)
name = await handle_name_input(ctx, "削除")
if not name:
return
try:
names = await get_names_from_firestore()
if name in names:
names_ref = db.collection('names')
query = names_ref.where('name', '==', name)
docs = query.stream()
for doc in docs:
doc.reference.delete()
await ctx.send(f'{name} を削除しました')
else:
await ctx.send(f'{name} は登録されていません')
except Exception as e:
print(f"Error in delete command: {str(e)}")
await ctx.send(f"エラーが発生しました: {str(e)}")
# 今日の記事を通知するコマンド
@bot.command(name='daily')
async def daily(ctx: commands.Context) -> None:
members = await get_names_from_firestore()
for member in members:
articles = get_articles(member)
if not articles:
await ctx.send(f"{member}の記事が見つかりませんでした。")
continue
today_articles = [
article for article in articles
if article['created_at'].split('T')[0] == str(date.today())
]
if not today_articles:
await ctx.send(f"{member}の今日の記事は見つかりませんでした。")
else:
await ctx.send(f"**{member}** の今日のQiita記事:")
await ctx.send("--------------------")
for article in today_articles:
await ctx.send(
f"タイトル: {article['title']}\n"
f"URL: {article['url']}\n"
f"投稿日: {article['created_at']}\n"
f"いいね数: {article['likes_count']}\n"
"---"
)
await ctx.send("--------------------")
# 今週の記事を通知するコマンド
@bot.command(name='weekly')
async def weekly(ctx: commands.Context) -> None:
members = await get_names_from_firestore()
for member in members:
articles = get_articles(member)
if not articles:
await ctx.send(f"{member}の記事が見つかりませんでした。")
continue
today = date.today()
monday = today - timedelta(days=today.weekday())
weekly_articles = [
article for article in articles
if article['created_at'].split('T')[0] >= str(monday)
]
if not weekly_articles:
await ctx.send(f"{member}の今週の記事は見つかりませんでした。")
else:
await ctx.send(f"**{member}** の今週のQiita記事:")
await ctx.send("--------------------")
for article in weekly_articles:
await ctx.send(
f"タイトル: {article['title']}\n"
f"URL: {article['url']}\n"
f"投稿日: {article['created_at']}\n"
f"いいね数: {article['likes_count']}\n"
"--------------------"
)
await ctx.send("--------------------")
# ヘルプコマンド
@bot.command(name='h')
async def help(ctx: commands.Context) -> None:
help_message = """
このBotの使い方:
- `!add`: 名前を追加します
- `!ls`: 登録されている名前一覧を表示します
- `!rm`: 名前を削除します
- `!daily`: 登録されている名前の今日のQiita記事を表示します
- `!weekly`: 登録されている名前の今週のQiita記事を表示します
"""
await ctx.send(help_message)
# 起動時に実行される処理
@bot.event
async def on_ready():
print(f'{bot.user} としてログインしました')
# Botの起動
bot.run(os.getenv('DISCORD_BOT_TOKEN'))
コードのポイント
1. Discord Botの初期化
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
-
intents.message_content = True
でメッセージコンテンツの取得を有効化します。 -
command_prefix='!'
でコマンドのプレフィックスを設定しています。
2. Firebaseの初期化
cred = credentials.Certificate("./firebase.json")
firebase_admin.initialize_app(cred)
db = firestore.client()
- ダウンロードした
firebase.json
を使用してFirebaseを初期化します。 - Firestoreのクライアントを取得しています。
3. メンバーの管理
-
追加(!add):Firestoreの
names
コレクションに新しいドキュメントを追加します。 - 一覧表示(!ls):Firestoreから全ての名前を取得し、表示します。
- 削除(!rm):指定した名前のドキュメントをFirestoreから削除します。
4. Qiita APIの使用
def get_articles(userid: str) -> List[Dict[str, Any]]:
url = f'https://qiita.com/api/v2/users/{userid}/items'
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"APIリクエストエラー: {e}")
return []
- ユーザーIDを使用して、そのユーザーの公開記事を取得します。
- エラー処理も含めています。
5. 日付によるフィルタリング
-
今日の記事(!daily):
created_at
の日付が今日の日付と一致する記事をフィルタリングします。 - 今週の記事(!weekly):今週の月曜日以降の記事をフィルタリングします。
6. コマンドの実装
各コマンドは@bot.command
デコレータを使用して実装されています。
-
!add
:メンバーの追加 -
!ls
:メンバー一覧の表示 -
!rm
:メンバーの削除 -
!daily
:今日の記事の表示 -
!weekly
:今週の記事の表示 -
!h
:ヘルプの表示
動作確認
Botを起動し、Discordサーバー上で以下のコマンドを試してみてください。
-
メンバーの追加
!add
Botが名前の入力を促してくるので、QiitaのユーザーIDを入力します。
-
メンバー一覧の表示
!ls
登録されているメンバーの一覧が表示されます。
-
今日の記事の表示
!daily
登録されているメンバーの今日のQiita記事が表示されます。
-
今週の記事の表示
!weekly
登録されているメンバーの今週のQiita記事が表示されます。
-
ヘルプの表示
!h
Botの使い方が表示されます。
まとめ
今回は、Firebase、Qiita API、discord.pyを使用して、指定したメンバーのQiita記事をDiscord上で通知するBotを作成しました。Firestoreを使用することで、データの永続化が可能になり、Botの再起動後も登録されたメンバー情報を保持できます。