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

More than 1 year has passed since last update.

Microsoft Teamsの非公式APIを使って自作Stream Deck

Last updated at Posted at 2023-04-06

はじめに

Microsoft Teamsを見ると、こんな設定項目がありました。
「アカウントの管理」-> 「プライバシー」の部分に。。

スクリーンショット 2023-04-06 20.44.20.png

気になって調べた所、Stream Deckなどと連携させる事が出来るようでした。
参考記事
https://pc.watch.impress.co.jp/docs/news/1475659.html

私は、Stream Deckを持っていないけど。。
試したいと思い、サードパーティ製APIを一般人でも使えないか調べると。。

海外の方が既にまとめてくれてました🙏
https://lostdomain.notion.site/Microsoft-Teams-WebSocket-API-5c042838bc3e4731bdfe679e864ab52a

という事で実際に使ってみたので、まとめました。

本記事の内容を実施する際には、あくまで自己責任で実施してくださいー
例外処理とか全然やってないですし、そもそも会社に怒られ・・・

TeamsのMTG情報がObnizに表示されている様子
ファイル名

Obnizでボタンを押下し、ミュートが切り替わる様子
( Teamsクライアントの映像ないですが、切り替わってます )
Untitled.gif

使ったもの

  • Obniz
    • Teams MTGの状況を表示
    • ボタン操作でミュート切り替え
  • Mac
    • node.jsのプログラム実行環境

主な手順

  • Step0
    • 実行環境の整備
  • Step1
    • WebsocketでTeams MTGのステータスを受け取る
  • Step2
    • 上記の情報をObnizへ表示する
  • Step3
    • +α ObnizのボタンでTeams MTGを操作する

実装してみる

それぞれ説明してますが、面倒な人は最後にコードの全文載せてるので、
一番下までスクロールしてくださいー!

Step0

node.jsの websocketライブラリやObnizライブラリをインストールする。
Nodeの実行環境はある前提で・・

npm install websocket
npm install obniz

Step1

Websocketを使い非公式APIでTeamsの情報を取得する。

var client = new WebSocketClient()
//ws接続成功時
client.on('connect', function(connect) {
    ~~中略~~
    connection.on('message', function(message) {
        if (message.type === 'utf8') {
			let str2json = JSON.parse(message.utf8Data)
			console.log(str2json)
			let canLeave = str2json.meetingUpdate.meetingPermissions.canLeave
			let teams_meetingState = str2json.meetingUpdate.meetingState
			let muted = teams_meetingState.isMuted ? "muted" : "unmuted"
			let camera = teams_meetingState.isCameraOn ? "camera on" : "camera off"
			let recording = teams_meetingState.isRecordingOn ? "Recording\n" : ""
			let raise_hand = teams_meetingState.isHandRaised ? "RaiseYourHand" : ""
			let display_str = `${muted}\n${camera}\n${recording}${raise_hand}`
            ~~以下略~~

以下のフォーマットでTeamsから情報が飛んできます。

サンプルデータ
{
   "apiVersion":"1.0.0",
   "meetingUpdate":{
      "meetingState":{
         "isMuted":false,
         "isCameraOn":true,
         "isHandRaised":false,
         "isInMeeting":false,
         "isRecordingOn":false,
         "isBackgroundBlurred":false
      },
      "meetingPermissions":{
         "canToggleMute":false,
         "canToggleVideo":true,
         "canToggleHand":false,
         "canToggleBlur":false,
         "canToggleRecord":false,
         "canLeave":false,
         "canReact":false
      }
   }
}

今回はObniz上に表示したい以下の情報を取得してます。
また、Obniz上で表示をするために、出力用の文字列も併せて定義しています。
e.g. isMutedがtrueの場合、muted

  • isMuted
    • 自身がミュートの場合、True
  • isCameraOn
    • 自身がカメラオンの場合、True
  • isRecordingOn
    • 参加している会議が録画中の場合、True
  • isHandRaised
    • 自身が挙手中の場合、True

Step2

obnizのライブラリを使い、以下のように表示を行います。
canLeaveが、
trueの場合 => MTGに参加中のため、ミュート等のステータスを表示
falseの場合 => そもそもMTGに参加していない状態なので、 Not in MTGを表示します。

			if(canLeave){
				obniz.display.print(display_str)
				in_mtg = true
			}else{
				obniz.display.print('Teams Status \n Not in MTG')
				in_mtg = false
			}

Step3

Obnizのボタンが押された際に、ミュートの状態を反転させる。
今回は、ミュート状態を切り替えてますが、リアクションをしたり、MTGから強制脱出することも可能です。

非公式APIでは、現在の状態を反転させることができるようなので、
Obnizのボタンが押されたタイミングでミュート状態を切り替えています。
manufactuerやdeviceは適当にOriginalを入れてます。

	button.onchange = function(voltage) {
	if(voltage & in_mtg){
		//現在とミュート・アンミュートを入れ替える。
		console.log("push")
		let now = Date.now()
		connection.sendUTF(`{"apiVersion":"1.0.0","service":"toggle-mute","action":"toggle-mute","manufacturer":"Original","device":"Original","timestamp":${now}}`)
	}
	}

最後に

今回Teams 非公式APIでStream Deckっぽいものを作りましたが、
ちゃんと動作するようで個人的には満足でした。
ボタンをもう少し用意する事ができれば、本家のStream Deck並みに操作する事ができると思います。

あくまで個人用での作成ですので、自身で使う際には会社等に怒られないようにしてください。
( 自己責任でお願いします🙏 )

サンプルコード
const Obniz = require("obniz")

let obniz = new Obniz("{{YOUR_Obniz_ID")
let teams_ws_url = "ws://localhost:8124?token={{YOUR_Teams_Token}}&protocol-version=1.0.0&manufacturer=Original&device=Original&app=Original&app-version=1.4"

let WebSocketClient =require('websocket').client

//ws connection
let connection = null
let in_mtg = false
//obniz接続
obniz.onconnect = async function () {
	obniz.display.clear()
	obniz.display.print('Teams Status \n Not in MTG')
	var button = obniz.wired("Keyestudio_Button", {signal:0, vcc:1, gnd:2});
	button.onchange = function(voltage) {
	if(voltage & in_mtg){
		//現在とミュート・アンミュートを入れ替える。
		console.log("push")
		let now = Date.now()
		connection.sendUTF(`{"apiVersion":"1.0.0","service":"toggle-mute","action":"toggle-mute","manufacturer":"Original","device":"Original","timestamp":${now}}`)
	}
	}
}



var client = new WebSocketClient()
//ws接続失敗時
client.on('connectFailed', function(error) {
    console.log('Connect Error: ' + error.toString())
});

//ws接続成功時
client.on('connect', function(connect) {
    console.log('WebSocket Client Connected')
	connection = connect
    connection.on('error', function(error) {
        console.log("Connection Error: " + error.toString())
    });
    connection.on('close', function() {
        console.log('WebSocket Client Closed')
    });
    connection.on('message', function(message) {
        if (message.type === 'utf8') {
			let str2json = JSON.parse(message.utf8Data)
			console.log(str2json)
			let canLeave = str2json.meetingUpdate.meetingPermissions.canLeave
			let teams_meetingState = str2json.meetingUpdate.meetingState
			let muted = teams_meetingState.isMuted ? "muted" : "unmuted"
			let camera = teams_meetingState.isCameraOn ? "camera on" : "camera off"
			let recording = teams_meetingState.isRecordingOn ? "Recording\n" : ""
			let raise_hand = teams_meetingState.isHandRaised ? "RaiseYourHand" : ""
			let display_str = `${muted}\n${camera}\n${recording}${raise_hand}`
			obniz.display.clear()
			if(canLeave){
				obniz.display.print(display_str)
				in_mtg = true
			}else{
				obniz.display.print('Teams Status \n Not in MTG')
				in_mtg = false
			}
			
			//console.log(str2json.meetingUpdate)
            console.log("Received: Data")
        }
    });
        
});

client.connect(teams_ws_url, 'echo-protocol')
3
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
3
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?