GolangでHolodule Botを作った話

これは みす54th Advent Calendar 2021 の4日目の記事です. 54代プロ研のI.TKです.

ホロライブとGolangの話をします.

はじめに

最近6期生がデビューしてホロライブがアツいですね!

少し前までVはおろかYouTubeすら見ていなかった自分ですが,最近は暇さえあればYouTubeでVを見ており,YouTube Premiumも契約してしまいました...

そうなるとホロライブメンバーの配信スケジュールが知りたい!となるのですが,そんなときに便利な配信スケジュールを掲載しているWebサイトHolodule を運営様が作ってくれています.

しかし,突発的に生える配信もあるし,気づくと時間を過ぎているなんてことも多々有りました. そこで,これをスクレイピングして取ってきて通知をするDiscord Botがあれば最高じゃね?と思ったので作りました.

作ったもの

毎日0:30にその日の配信スケジュールを通知する"daily"と,配信の1〜2時間前に通知をする"coming-soon"の2つの通知機能を作りました. Discord鯖にはdailyとcoming-soon用の2つのチャンネルを作っておいて,そこに通知を投げます.

Daily

https://schedule.hololive.tv/ の画像を使用しています.

上の画像は一部で,この後にズラッとまだ並んでます.

Coming Soon

https://schedule.hololive.tv/ の画像を使用しています.

環境と使用ライブラリ

なぜGolang?

普段PythonDjangoばかり使っている自分ですが,ずっとPythonばかり使っているのもアレだし,何より静的な型がほしい!と思うようになったので,気になっていたGolangを使うことにしました. 幸いなことにGoにはgoqueryというスクレイピング用ライブラリがあったので,そこには困りませんでした. (ちなみにPythonにもBeautiful Soupというよく知られたスクレイピング用ライブラリがあります.)

仕様

スクレイピング方法

配信のデータをhtmlから抜き取らなくてはいけないわけですが,特にその項目ごとにIDが振られてるようなことはなかったため,ダグ名とclass名で無理やり取得しました.

取得項目

  • 配信者
    • 配信者名
    • 配信者アイコンURL
  • 配信日時
  • サムネイル画像URL

DB

DB設計は下図のような感じ.基本は上の取得項目を保存して,それに加えて"coming-soon"の通知をしたかどうかを判断するために notified_at フィールドを加えています.

f:id:ITK13201:20211121020806p:plain

通知の流れ

dailyについては簡単で,毎日0:30にholoduleからデータを取ってきて当日の分だけDiscord APIを叩いて通知を送るだけです.

coming-soonについては,通知をしたかどうかを判断するため,DBにデータを格納する必要がありました. まず,毎時30分にholoduleからデータを取ってきて,新規追加分と更新をDBに反映します. 次に,現在時刻が配信時刻の1〜2時間前であるもののうち,通知済みでない配信をDBから取得してDiscord APIを叩いて通知を送ります. それが成功したら,notified_atに現在時刻を挿入し,通知済みとします.

Discord API Embed(s)の落とし穴

※ EmbedというのはDiscordに文字・リンク・画像なんかをまとめてカードのように表現して送ることができるメッセージの形態です.リンクをチャット欄に貼ったときにも出てきますね.アレです.これを使っています.

Discord に通知を送るためにDiscord APIのEmbedを使ったのだが,2つの落とし穴がありました.

まず1つめは,dailyはその日のすべての配信を取得するため相当な数があり,Embedでひとつひとつ通知をすると「API叩きすぎだ.少し待て.」と怒られました.Hololiveって一日にこんなに配信してるんですね... すごい

2つめは,1つめの問題を解決するためにEmbedsというEmbedをまとめて送れるのもあり,これを使おうとしたのですが,1つあたり10個のEmbedしかまとめることが出来ないそうで,分けて送る必要がありました. さらに,Embedsを使っても「API叩きすぎだ.少し待て.」と怒られ,仕方なく最初のテキストのところはWebhookで送ることにして,やっとAPIの呼び出し制限を回避しました.

これでも怒られたらどうしようもなかったので本当に良かったです...

おわりに

Golangを使って思ったのは

型って素晴らしい!!!!!!!!!!!!!!!!

これに尽きます.Pythonでも型ヒントはあるんですがつけやがらねえ奴もいるし,そもそもライブラリに付いてなかったりもします. まあ,型がほしいなら別の言語使えってことですね.ハイ.

あと,今回ORMを使わずにSQLを直で書いて実行するようにしました. sqlxがうまい具合にstructにマッピングしてくれたので,そこまで大変ではなかったですが,まあ,ORMに頼らない分やることは増えましたね. 以前からそれぞれのORMの学習コストが高いのと,SELECTでJOINするときにそのORMに対応しているものをドキュメントから漁らなきゃいけなかったこともあって,もやもやしていたのでいい経験になったかと思います.プロジェクトでORMを使うかどうかは,どこまでカバーするかも含めて適材適所って感じですかね.

今回実装したコードはGithubに載せてあります. よかったら見てってネ.もしアドバイスなどあればDiscordでもSlackでもTwitterでも何でもいいのでくれたら泣いて喜びます.

github.com

最後に,これを作ったおかげて配信の見逃しが少なくなりました.ですが,ホロライブメンバー全員分の配信通知がDiscordに来るので,すごいことになっています.通知をする配信者を絞る機能を早急に作らなければいけないなと感じています.

Hololiveはいいぞ.

watchdogとblackでpythonの自動フォーマットをする

はじめに

pythonソースコードををセーブ時に自動でフォーマットする方法はいくつかあると思う. VScodeであればformatOnSaveを設定すればいいし,PycharmであればFile Watcherを使えばいい. ただ,これらはエディタ・IDEに依存する方法のため,共同開発においてはではなかなか使いづらい. どのような開発環境でも使えるようにターミナルで実行する自動フォーマッタがあればいいなと思った.

環境

Ubuntu 18.04LTS Desktop

時期

2021年5月現在

Watchdog とは

Python製のファイル監視ツール.ファイルの変更を検知し,スクリプトを実行できる.

Black とは

Pythonのフォーマッタ.あまり設定の自由度は高くないが,その分導入に時間がかからない. まだベータ版しかない.

導入済みであると想定してるもの

導入手順

pipenv のインストール

今回はpipenvの仮想環境を使って導入する. プロジェクトディレクトリの直下に仮想環境を作りたいので,まず,以下を~/.bashrcに追記する.

export PIPENV_VENV_IN_PROJECT=true

~/.bashrc を読み込むために以下のコマンドを実行する.

source ~/.bashrc

次に,pipenv をインストールする.

pip install pipenv

そして,仮想環境を作る.

pipenv install

blackとwatchdogとその他依存ライブラリをインストールする.

blackはベータ版しかないため,バージョンを指定する必要がある. --devをつけることで,開発環境のみにインストールすることができる.

pipenv install --dev black==20.8b1 watchdog pyyaml argh

tricks.ymlの作成

watchdogをインストールするとwatchmedoコマンドが使える. watchdogをimportして自分でスクリプトを書くこともできるのだが,watchmedoのほうが手軽なので,こっちを採用した.

tricks.ymlはwatchmedoコマンドを実行する際の設定ファイル. tricks.ymlファイルを作成し,以下のコードを書き込む.

tricks:
    - watchdog.tricks.ShellCommandTrick:
        shell_command: "black ${watch_src_path}"
        patterns: ['*.py']
        ignore_patterns: ['*/**/migrations/*.py']
        ignore_directories: true
        drop_during_process: true

shell_commandはファイルが変更され,セーブしたときに実行されるコマンド.${watch_src_path}は変更されたファイル名を示す. ignore_patternsは無視するファイル名のパターン.自分はDjangoをよく使うので,migrationsディレクトリ以下のファイルは無視するようにしている.

pyproject.tomlの作成

blackの設定ファイル.pyproject.tomlファイルを作成し,以下のコードを書き込む.

[tool.black]
line-length = 88
include = '\.pyi?$'
exclude = '''
/(
    \.git
  | \.tox
  | \.venv
  | _build
  | __pycache__
  | buck-out
  | build
  | dist
  | migrations
)/
'''

line-lengthは改行するか否かを決定する文字数.includeとexcludeはそれぞれ含むファイルと無視するファイルのパターン名.

実行

まず,pipenvの仮想環境に入る.

pipenv shell

以下のコマンドを実行することで,自動フォーマッタを起動することができる. 止めるときにはCtrl + Cで止められる.

watchmedo tricks-from tricks.yml

テスト

試しにmain.pyというファイルを作成して,自動フォーマッタを実行してみる.

f:id:ITK13201:20210503001253g:plain

Pipenv Script の設定

このままでも良いのだが,コマンドも長く実用的ではないため,Pipfileスクリプトを設定し,簡単に実行できるようにする. Pipfileに以下のコードを追記する.

[scripts]
watch = "watchmedo tricks-from tricks.yml"

これにより,以下のコマンドだけで仮想環境でフォーマッタを起動することができる.

pipenv run watch

感想

これで個々の開発環境によらず,フォーマッタを設定することができたので,共同開発がやりやすくなったのではないかと思う. 今回使ったファイル群はGithubにpushしているので,よかったら参考にしてほしい.

github.com