先日多言語Webニュースアプリを作りました。 通勤電車で当日のニュースをチェックしながら、外国語を勉強できるので、まあまあ使いやすかったです。
実はこのWebを開発する1年ほど前に、一度Telegram botで多言語ニュースチャットボットを開発していました。今日はそれについて紹介したいと思います。
https://github.com/aibazhang/multitrue-bot
事前準備
Telegram bot
Telegramは日本ではあまり人気がないですが、Telegramのアカウントを作るだけでAPIキーを取得できるので、ざくっとボット作りたい場合は使い勝手がけっこういいです。また、良さげのSDKもあります↓。そういう意味ではSlackも同じですが、Telegramは感覚的にLineに近いので手軽さで勝っていると思います。
https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples
ニュースの取得
ニュースの取得方法は先日開発したWebアプリと同じくNewsAPI利用して取得しています。なぜNewsAPIを採用したかについては半年前の記事で詳細を説明したので、割愛します。
実装
https://core.telegram.org/ に参照してボットの初期設定が完了したら、実装に入ります。 フローは以下となります。
/start
でニュースボットを起動- 国・地域を選ぶ
- ニュースのジャンルを選ぶ
- 終了あるいは2.に戻る
全体フロー
SDKが提供してくれた下記3つのhandlerを利用しています。
ConversationHandler
: 先ほど設計したフローに基づいてhandlerを定義する。CommandHandler
: コマンドによって発火される。今回は「入り口 (entry point)」として使うCallbackQueryHandler
: 他のhandlerの返り値によって発火される
def main():
updater = Updater(
token=json.load(open(KEY_PATH / "keys.json", "r"))["telegram_key"],
use_context=True,
)
dispatcher = updater.dispatcher
country_pattern = "^us|jp|cn|tw|kr|gb$"
headlines_pattern = "^us|jp|cn|tw|kr|gb business|entertainment|general|health|science|sports|technology|$"
conv_handler = ConversationHandler(
entry_points=[CommandHandler("start", start)],
states={
"CATEGORY": [CallbackQueryHandler(select_category, pattern=country_pattern)],
"HEADLINES": [CallbackQueryHandler(get_news, pattern=headlines_pattern)],
"START OVER OR NOT": [
CallbackQueryHandler(start_over, pattern="^start over$"),
CallbackQueryHandler(end, pattern="^end$"),
],
},
fallbacks=[CommandHandler("start", start)],
)
dispatcher.add_handler(conv_handler)
updater.start_polling()
updater.idle()
メニュー
続いてはcallback関数を見ていきましょう。start
関数はwelcomeメッセージをユーザに送って、ユーザに国・地域を選んでもらいます。その後、select_category
関数でニュースのジャンルを選んでもらいます。
引数context
とupdate
context.user_data
: ボットを利用してユーザの情報を取得するcontext.bot
: ボット自体を操作する(今回はメッセージを送るだけ)のに使うupdate
: メッセージを更新するのに使う
def start(update, context):
user = update.message.from_user
logger.info("User {} started the conversation.".format(user))
for i, v in vars(user).items():
context.user_data[i] = v
welcome_message = (
"Hello, {}\n"
"This is JC News bot🗞️🤖\n\n"
"You can get Top News Headlines for a Country and a Category from here. \n\n".format(user.first_name)
)
print(context)
keyborad = [
[
InlineKeyboardButton("🇺🇸", callback_data="us"),
InlineKeyboardButton("🇯🇵", callback_data="jp"),
InlineKeyboardButton("🇹🇼", callback_data="tw"),
],
[
InlineKeyboardButton("🇰🇷", callback_data="kr"),
InlineKeyboardButton("🇬🇧", callback_data="gb"),
InlineKeyboardButton("🇨🇳", callback_data="cn"),
],
]
reply_markup = InlineKeyboardMarkup(keyborad)
context.bot.send_message(chat_id=update.effective_chat.id, text=welcome_message)
update.message.reply_text("Please Choose a Country🤖", reply_markup=reply_markup)
return "CATEGORY"
def select_category(update, context):
logger.info("User data from context {}".format(context.user_data))
logger.info("Chat data from context {}".format(context.chat_data))
logger.info("Bot data from context {}".format(context.bot_data))
query = update.callback_query
query.answer()
country = query.data
keyborad = [
[
InlineKeyboardButton("👩🏼💻Technology", callback_data=country + " technology"),
InlineKeyboardButton("🧑💼Business", callback_data=country + " business"),
],
[
InlineKeyboardButton("👨🏻🎤Entertainment", callback_data=country + " entertainment"),
InlineKeyboardButton("👩🏻⚕️Health", callback_data=country + " health"),
],
[
InlineKeyboardButton("👨🏿🔬Science", callback_data=country + " science"),
InlineKeyboardButton("🏋🏼♂️Sports", callback_data=country + " sports"),
],
[InlineKeyboardButton("🌎General", callback_data=country + " general")],
]
reply_markup = InlineKeyboardMarkup(keyborad)
query.edit_message_text(text="Please Choose a Category🤖", reply_markup=reply_markup)
return "HEADLINES"
ニュースを取得
最後は最もコアとなるニュースを取得するロジックです。country
とcategory
によってNewAPIから最新ホットラインニュースを取得しフォーマットした後、ユーザにメッセージを送ります。
具体的な詳細は説明しませんが、NewsAPICollector
というクラスでNewAPIをラッピングしています。
https://github.com/aibazhang/multitrue-bot/blob/main/src/news/collector.py
country
とcategory
はcallback_dataは上記のcallback関数から取得しています。Slack Appのpayloadと似たような感じです。
def get_news(update, context):
query = update.callback_query
query.answer()
country, category = query.data.split(" ")
nac = NewsAPICollector(country=country, category=category, page_size=10, print_format="telebot")
nac.collcet_news()
news_list = nac.news_list
news_list = news_list[:5] if len(news_list) > 5 else news_list
context.bot.send_message(
chat_id=update.effective_chat.id,
text="Top {} latest news of [{}] [{}] for you🤖".format(len(news_list), country.upper(), category.upper()),
)
for news in news_list:
context.bot.send_message(chat_id=update.effective_chat.id, text=news)
keyboard = [
[
InlineKeyboardButton("Let's do it again!", callback_data="start over"),
InlineKeyboardButton("I've had enough...", callback_data="end"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
context.bot.send_message(
chat_id=update.effective_chat.id,
text="Do you want to start over?🤖",
reply_markup=reply_markup,
)
return "START OVER OR NOT"
デモはリポジトリのREADME.md
のgifから確認できます。
https://github.com/aibazhang/multitrue-bot/
最後に
Telegram bot経由でモバイル開発経験がゼロの人(筆者)もニュースチャットボットを作りました。もちろんニュースだけではなく、定期的に株価や不動産価格を取得したり、英単語帳を作って復習のリマインドしたりするようなWebアプリあるいはモバイルアプリを開発するほどでもないミニアプリに適しているのではないでしょうか?