はじめに
Microsoft Teamsを見ると、こんな設定項目がありました。
「アカウントの管理」-> 「プライバシー」の部分に。。
気になって調べた所、Stream Deckなどと連携させる事が出来るようでした。
参考記事
https://pc.watch.impress.co.jp/docs/news/1475659.html
私は、Stream Deckを持っていないけど。。
試したいと思い、サードパーティ製APIを一般人でも使えないか調べると。。
海外の方が既にまとめてくれてました🙏
https://lostdomain.notion.site/Microsoft-Teams-WebSocket-API-5c042838bc3e4731bdfe679e864ab52a
という事で実際に使ってみたので、まとめました。
本記事の内容を実施する際には、あくまで自己責任で実施してくださいー
例外処理とか全然やってないですし、そもそも会社に怒られ・・・
Obnizでボタンを押下し、ミュートが切り替わる様子
( Teamsクライアントの映像ないですが、切り替わってます )
使ったもの
- 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')