LoginSignup
3
3

More than 1 year has passed since last update.

Teamsの個人チャットへ自動送信する(2段階認証回避)

Last updated at Posted at 2021-10-03

はじめに

 お久しぶりです。プロコン間に合いませんでした。次はきっと間に合わせます(次があったら)。
 今回の記事は、Teamsの個人チャットへ、メッセージを自動送信するプログラムの作成について書いたものです。バイト先で必要になったので、作りました。
 意外と躓きポイントがあったので大変でした。躓いたのは、ログイン(2段階認証)と、seleniumの要素の取得(classでfindする時に空白を含んではいけない)です。一応、githubにプログラムを公開しておきます。使う人がいればですが、好きに使ってください。

概要

 本記事で解説する手法は、「Teamsの個人チャットへメッセージを自動送信する」ものである。
 従来のTeasmへ自動送信する手段としてIncoming Webhook[1]というツールがあるが、チーム単位でしか操作できない特徴があり、自動送信される相手はそのチームに参加する必要がある。
 本手法では「Selenium」と「起動済みのchorme」を用いることで、そういった縛りを受けず、個人へのメッセージ送信を可能とした。起動済みのchromeを用いるのは、seleniumの操作時に2段階認証のステップを回避する為である。
 本手法のデメリットとしては、保守管理のコストである。Web版のアプリを、無理やりSeleniumで操作している為、Teamsの更新に伴ってプログラムを更新する必要がある。おそらく、Teamsの開発者にとっては、自由に自動操作を行って欲しくないのだろう。

※2021/10/03時点の記載
webhook.JPG

[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上で動作している点にご注意ください。

①検索バーに、送信相手を特定するキーワード、を入力。
検索1_検索バー.JPG
②検索結果が表示される。
検索2_検索結果.JPG
③タブ「ユーザー」をクリックする。
検索3_検索結果.JPG
④チャットのメッセージボックスに文字列を入力し、送信ボタンを押す。
検索4_メッセージ送信.JPG

公開するソースコード

 github上に公開します。好きに使ってください(もちろん自己責任で)。run.pyを実行すると、ブラウザが起動し、自動送信が始まります(送信ボタンは押さない)。また、ブラウザが起動しない場合は、chrome.batのchrome.exeへのパスを設定すると解決するかもしれないです。

実現方法

1.起動済みのブラウザを立ち上げ、Teamsへログインする。

 まず、以下のコマンドでchrome.exeを立ち上げ([2]参照)、Web版のTeamsへログインします。その際、次回のアクセス時に、ログインを省略できるように設定します。それにより、SeleniumからTeamsへの自動操作が可能となります。
 ログインなどの情報はすべて、--user-data-dir=に格納されています。もし、起動済みのブラウザを使わずに、SeleniumでTeamsへログインしようとした場合、2段階認証の手順で躓くことになります。また、この手順は扱いは、いわば初期設定となります。

portを固定しchromeを開く(cmd上)
"C:\Program Files\Google\Chrome\Application\chrome.exe" -remote-debugging-port=9222 --user-data-dir="適当なパス(chromeのデータがいっぱいできても良い場所)"

ログインの様子

1.chrome.exeを引数を指定しながら開く。
初期設定1.JPG
2.Web版のTeamsへログインする。
初期設定2.JPG
3.ユーザー情報を入力する際、「パスワードを保存しますか?」で「保存」を押す。
初期設定3.JPG
4.2段階認証を行う。
初期設定4.JPG
5.「今後このメッセージを表示しない」にチェックを入れ、サインインの状態を維持に「はい」と答える。
初期設定5.JPG

2.seleniumでメッセージの自動送信を行う。

 手順1が完了したら、temasへの自動操作が可能となります。後はselenium(python)で、ゴリゴリとプログラムを書いていくだけです。ただし、[2]でも述べている通り、起動済みのブラウザを操作する点に注意してください。つまり、必ず以下の手順でプログラムを起動する必要があります。その為、公開するソースコードでは、run.pyによりこの処理を行っています。

1.chrome.exeを引数指定で起動
2.seleniumのプログラムを起動

自動送信のソースコード

以下のソースコードを書いておきます。
 手順としては、以下の様な感じです。

①検索欄にkeywardを入力し、検索する。
②検索結果画面のタブ「ユーザー」をクリックする。
③ユーザー一覧の一番上の枠をクリックする。
④メッセージボックスにmessageを入力し、送信ボタンを押す。

 keywardが送信相手を特定する文字列で、messageが自動送信するメッセージの文字列です。この時、keywardでヒットしたユーザーのうち、一番上のユーザーへメッセージを送ります。よって、ヒットする人が1人となるような情報が望ましいです。
 また、一番最後の行は、送信ボタンを押す処理であるため、コメントアウトしています。実際に使う際は、コメントを外してください。

teams_sendmsg.py
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を取得できます。
chrome_検証.JPG

 また、クラス名でどうしても指定したい場合がありました。その際、くそ長いクラス名を指定してたのですが、上手くいきませんでした。どうやら、by_class_nameでは、スペースを含むクラス名は上手くいかないようです。
 @hanonaibaobabuさんが解決していました。とても参考になりました。解決策としては、css_selectorで指定すると良いそうです。先頭に.を置き、スペースを.に置き換えると上手くいきます。

おわりに

 今回は、Teamsで個人チャットへのメッセージ自動送信するためのプログラムを作りました。当初の予定では、Incoming Webhookを使う予定でしたが、結局seleniumによるごり押しの方法となってしました。なので、長期にわたる安定な稼働を行うのは難しそうですね。
 Incoming Webhookがもっと柔軟なら良いんですけどね。そういった簡単な自動化、は悪戯されやすいと判したのでしょうかね。
 あと、プロコン間に合いませんでした。論文・テストが重なって、できませんでした(言い訳)。今度こそ頑張ってみます!
 ご質問・ご指摘などございましたら、気軽にコメントください。それじゃ!

3
3
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
3
3