はじめに
お久しぶりです。プロコン間に合いませんでした。次はきっと間に合わせます(次があったら)。
今回の記事は、Teamsの個人チャットへ、メッセージを自動送信するプログラムの作成について書いたものです。バイト先で必要になったので、作りました。
意外と躓きポイントがあったので大変でした。躓いたのは、ログイン(2段階認証)と、seleniumの要素の取得(classでfindする時に空白を含んではいけない)です。一応、githubにプログラムを公開しておきます。使う人がいればですが、好きに使ってください。
概要
本記事で解説する手法は、「Teamsの個人チャットへメッセージを自動送信する」ものである。
従来のTeasmへ自動送信する手段としてIncoming Webhook[1]というツールがあるが、チーム単位でしか操作できない特徴があり、自動送信される相手はそのチームに参加する必要がある。
本手法では「Selenium」と「起動済みのchorme」を用いることで、そういった縛りを受けず、個人へのメッセージ送信を可能とした。起動済みのchromeを用いるのは、seleniumの操作時に2段階認証のステップを回避する為である。
本手法のデメリットとしては、保守管理のコストである。Web版のアプリを、無理やりSeleniumで操作している為、Teamsの更新に伴ってプログラムを更新する必要がある。おそらく、Teamsの開発者にとっては、自由に自動操作を行って欲しくないのだろう。
[1] Microsoft Teamsのテクニカルドキュメテーション Webhook とコネクタ ,https://docs.microsoft.com/ja-jp/microsoftteams/platform/webhooks-and-connectors/what-are-webhooks-and-connectors
目的と対象者
本記事の目的は、以下の様になっている。
- Teamsの”個人チャットへ”メッセージを自動送信する
また、以下の条件を満たしている必要がある。
- pythonを使える人
- seleniumが使える人(chromeを使える人)
- 起動済みのブラウザをSeleniumで操作できる人([2]を参照)
[2] PythonのSeleniumを使って、起動済みのブラウザを操作する。, https://qiita.com/mimuro_syunya/items/2464cd2404b67ea5da56
結果の様子
読者とのミスマッチを避けるため、実行した様子の画像を列挙してきます。以下の画像は、すべてseleniumが自動的に行っています。また、WEBブラウザ版のTeams上で動作している点にご注意ください。
①検索バーに、送信相手を特定するキーワード、を入力。
②検索結果が表示される。
③タブ「ユーザー」をクリックする。
④チャットのメッセージボックスに文字列を入力し、送信ボタンを押す。
公開するソースコード
github上に公開します。好きに使ってください(もちろん自己責任で)。run.pyを実行すると、ブラウザが起動し、自動送信が始まります(送信ボタンは押さない)。また、ブラウザが起動しない場合は、chrome.batのchrome.exeへのパスを設定すると解決するかもしれないです。
実現方法
1.起動済みのブラウザを立ち上げ、Teamsへログインする。
まず、以下のコマンドでchrome.exeを立ち上げ([2]参照)、Web版のTeamsへログインします。その際、次回のアクセス時に、ログインを省略できるように設定します。それにより、SeleniumからTeamsへの自動操作が可能となります。
ログインなどの情報はすべて、--user-data-dir=
に格納されています。もし、起動済みのブラウザを使わずに、SeleniumでTeamsへログインしようとした場合、2段階認証の手順で躓くことになります。また、この手順は扱いは、いわば初期設定となります。
"C:\Program Files\Google\Chrome\Application\chrome.exe" -remote-debugging-port=9222 --user-data-dir="適当なパス(chromeのデータがいっぱいできても良い場所)"
ログインの様子
1.chrome.exeを引数を指定しながら開く。
2.Web版のTeamsへログインする。
3.ユーザー情報を入力する際、「パスワードを保存しますか?」で「保存」を押す。
4.2段階認証を行う。
5.「今後このメッセージを表示しない」にチェックを入れ、サインインの状態を維持に「はい」と答える。
2.seleniumでメッセージの自動送信を行う。
手順1が完了したら、temasへの自動操作が可能となります。後はselenium(python)で、ゴリゴリとプログラムを書いていくだけです。ただし、[2]でも述べている通り、起動済みのブラウザを操作する点に注意してください。つまり、必ず以下の手順でプログラムを起動する必要があります。その為、公開するソースコードでは、run.pyによりこの処理を行っています。
1.chrome.exeを引数指定で起動
2.seleniumのプログラムを起動
自動送信のソースコード
以下のソースコードを書いておきます。
手順としては、以下の様な感じです。
①検索欄にkeyward
を入力し、検索する。
②検索結果画面のタブ「ユーザー」をクリックする。
③ユーザー一覧の一番上の枠をクリックする。
④メッセージボックスにmessage
を入力し、送信ボタンを押す。
keyward
が送信相手を特定する文字列で、message
が自動送信するメッセージの文字列です。この時、keyward
でヒットしたユーザーのうち、一番上のユーザーへメッセージを送ります。よって、ヒットする人が1人となるような情報が望ましいです。
また、一番最後の行は、送信ボタンを押す処理であるため、コメントアウトしています。実際に使う際は、コメントを外してください。
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
keyward = "検索する名前 or メールアドレスor etc(検索結果は1人のみ)"
message = "自動送信するメッセージ"
# make a driver by already opened chrome
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)
wait = WebDriverWait(driver, 15)
# get target URL
driver.get('https://teams.microsoft.com')
WebDriverWait(driver, 60).until(EC.presence_of_all_elements_located)
# input keyward
wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="searchInputField"]')))
EleSearch = driver.find_element_by_xpath('//*[@id="searchInputField"]')
EleSearch.clear()
EleSearch.send_keys(keyward)
EleSearch.send_keys(Keys.RETURN)
# click area of user
wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="search-result-tabs"]/li[2]/a')))
driver.find_element_by_xpath('//*[@id="search-result-tabs"]/li[2]/a').click()
# click first user
wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="peopleSearchContent-0"]/div/div[2]')))
driver.find_element_by_xpath('//*[@id="peopleSearchContent-0"]/div/div[2]').click()
# send message for user
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.cke_wysiwyg_div.cke_reset.cke_enable_context_menu.cke_editable.cke_editable_themed.cke_contents_ltr.cke_show_borders')))
EleMsgBox = driver.find_element_by_css_selector('.cke_wysiwyg_div.cke_reset.cke_enable_context_menu.cke_editable.cke_editable_themed.cke_contents_ltr.cke_show_borders')
EleMsgBox.clear()
EleMsgBox.send_keys(message)
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.app-svg.icons-send.icons-rtl-flip')))
EleSendBtn = driver.find_element_by_css_selector('.app-svg.icons-send.icons-rtl-flip')
#EleSendBtn.click()
躓いたポイント
とにかく、seleniumの要素取得driver.find_なんちぁら
で躓きました。私もまだよく理解していませんが、クリックしたいだけならxpath
で指定しちゃっても良いのかな、って感想です。xpath
の取得は、chromeブラウザ上の右クリック「検証」で要素を探して(以下画像のクリックするボタン)、コード上で右クリック「copy」からxpath
を取得できます。
また、クラス名でどうしても指定したい場合がありました。その際、くそ長いクラス名を指定してたのですが、上手くいきませんでした。どうやら、by_class_nameでは、スペースを含むクラス名は上手くいかないようです。
@hanonaibaobabuさんが解決していました。とても参考になりました。解決策としては、css_selectorで指定すると良いそうです。先頭に.
を置き、スペースを.
に置き換えると上手くいきます。
おわりに
今回は、Teamsで個人チャットへのメッセージ自動送信するためのプログラムを作りました。当初の予定では、Incoming Webhookを使う予定でしたが、結局seleniumによるごり押しの方法となってしました。なので、長期にわたる安定な稼働を行うのは難しそうですね。
Incoming Webhookがもっと柔軟なら良いんですけどね。そういった簡単な自動化、は悪戯されやすいと判したのでしょうかね。
あと、プロコン間に合いませんでした。論文・テストが重なって、できませんでした(言い訳)。今度こそ頑張ってみます!
ご質問・ご指摘などございましたら、気軽にコメントください。それじゃ!