0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Firebase + QiitaAPI + discord.py 】追加したメンバーのQiitaの記事を通知してくれるbot作ってみた

Posted at

はじめに

こんにちは!今回は、FirebaseQiita API、そしてdiscord.pyを組み合わせて、指定したメンバーのQiita記事をDiscord上で通知してくれるBotを作成してみました。

このBotは以下の機能を持っています:

  • メンバーの追加・削除
  • 登録されたメンバー一覧の表示
  • 今日投稿された記事の通知
  • 今週投稿された記事の通知

それでは、実際のコードとともに作り方を解説していきます。

環境設定

必要なライブラリのインストール

まずは、必要なライブラリをインストールします。

pip3 install discord.py firebase-admin python-dotenv requests

Firebaseの設定

  1. Firebaseプロジェクトの作成

    Firebase Consoleで新しいプロジェクトを作成します。

  2. サービスアカウントの作成

    プロジェクトの設定からサービスアカウントを選択し、新しい秘密鍵を生成してダウンロードします。このファイルをプロジェクトディレクトリにfirebase.jsonとして保存します。

  3. Firestoreの有効化

    データベースとしてFirestoreを使用します。Firestoreを有効化し、セキュリティルールを適切に設定します。

Discord Botの作成

  1. Discord Developer Portalでアプリケーションを作成

    Discord Developer Portalで新しいアプリケーションを作成します。

  2. Botの追加

    アプリケーションにBotを追加し、Botのトークンを取得します。このトークンは後で使用します。

  3. 必要なIntentの有効化

    Botの設定で「MESSAGE CONTENT INTENT」を有効にします。

  4. 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サーバー上で以下のコマンドを試してみてください。

  1. メンバーの追加

    !add
    

    Botが名前の入力を促してくるので、QiitaのユーザーIDを入力します。

  2. メンバー一覧の表示

    !ls
    

    登録されているメンバーの一覧が表示されます。

  3. 今日の記事の表示

    !daily
    

    登録されているメンバーの今日のQiita記事が表示されます。

  4. 今週の記事の表示

    !weekly
    

    登録されているメンバーの今週のQiita記事が表示されます。

  5. ヘルプの表示

    !h
    

    Botの使い方が表示されます。

まとめ

今回は、Firebase、Qiita API、discord.pyを使用して、指定したメンバーのQiita記事をDiscord上で通知するBotを作成しました。Firestoreを使用することで、データの永続化が可能になり、Botの再起動後も登録されたメンバー情報を保持できます。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?