GASとslackでヘルプデスクフォームを作った話(スラッシュコマンド版 コード編)
GASとslackでヘルプデスクフォームを作った話(スラッシュコマンド版 準備編)の続きです。
処理の流れ
doPostで受けとったデータの内容によって分岐するようにしています。
フォームからのリクエストの場合
- フォームのcallback_idをチェック
- 質問者のslackのユーザーIDからメールアドレスを取得
- 内容をGSSに書き込む
- 質問者をチャンネルへ招待
- 内容をチャンネルへポスト
コード全体
コメントアウトで説明を入れています。
function doPost(e) { //スクリプトのプロパティからトークンやIDを取得 var verifiy_token = PropertiesService.getScriptProperties().getProperty('verifiy_token'); //slackアプリ Basic InformationのVerification Token var bot_token = PropertiesService.getScriptProperties().getProperty('bot_token'); //slackアプリ OAuth & PermissionsのBot User OAuth Access Token var channel_id = PropertiesService.getScriptProperties().getProperty('channel_id'); //#helpdeskのチャンネルID var gss_id = PropertiesService.getScriptProperties().getProperty('gss_id'); //スプレッドシートのID var sup_mem1 = PropertiesService.getScriptProperties().getProperty('sup_mem1'); //サポートメンバー1のslackUserID //メンバー数に応じて、必要があれば追加してください。(スクリプトプロパティも) //var sup_mem2 = PropertiesService.getScriptProperties().getProperty('sup_mem2'); //サポートメンバー2のslackUserID var time = new Date(); //投稿時間作成用に現在時刻を取得 //verification tokenの検証 var verification_token = e.parameter.token || JSON.parse(e.parameter.payload).token || null; //リクエストされたトークンを格納 if (verification_token !== verifiy_token) { // アプリのトークンと一致しない場合終了 return ContentService.createTextOutput(); } //----------------------------------------スラッシュコマンドから投稿された場合の処理 if (e.parameter.command === "/helpjosys") { var created_dialog = createDialog(e,bot_token); //ダイアログを作成する var response = UrlFetchApp.fetch("https://slack.com/api/dialog.open", created_dialog); //チャンネルへポストする return ContentService.createTextOutput(); //空文字をreturnしないとダイアログが消えない //----------------------------------------フォーム(Dialog)から投稿された場合の処理 } else { var p = JSON.parse(e.parameter.payload); var s = p.submission; if (p.callback_id === "helpdesk_dialog") { //想定しているフォームからの投稿かチェック //質問者のslack User IDから、メールアドレスと取得 var user_email = findUserBySlackID(p.user.id,bot_token) //スプレッドシートへ記録 var spread_sheet = SpreadsheetApp.openById(gss_id); var sheet_name = "helplist"; var no = spread_sheet.getSheetByName(sheet_name).getLastRow() //現在の最終行を管理Noにする spread_sheet.getSheetByName(sheet_name).appendRow( [no, time, user_email, p.user.id, s.username, //s.*****の*****はfunction createDialogで作成したelementsのnameです s.urgency, s.question_content ] ) //質問者をチャンネルへ招待 var created_invite = createInvite(p.user.id,bot_token,channel_id) var response = UrlFetchApp.fetch("https://slack.com/api/conversations.invite", created_invite); //チャンネルへポスト var message_data = "<@" + sup_mem1 + ">" // サポートメンバー増があれば必要に応じて追加<@" + sup_mem2 + ">" + "\n<@" + p.user.id + "> から問い合わせがありました。" + "\n\n*No*\n" + no + "\n\n*氏名*\n" + s.username + "\n\n*緊急度*\n" + s.urgency + "\n\n*問い合わせ内容*\n" + s.question_content var created_message = createMessage(message_data,bot_token,channel_id) var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", created_message); } return ContentService.createTextOutput(); } return ContentService.createTextOutput(); } //----------------------------------------フォーム(Dialog)作成 function createDialog(e,bot_token){ var trigger_id = e.parameter.trigger_id; var dialog = { "token": bot_token, "trigger_id": trigger_id, "dialog": JSON.stringify( { "callback_id": "helpdesk_dialog", "title": "ヘルプデスクフォーム", "submit_label": "送信する", "elements": [ { "type": "text", "label": "氏名", "name": "username", "placeholder": "佐藤裕也(`ェ´)ピャー" //記入例を表示させたい場合placeholderを使用 }, { "type": "select", "label": "緊急度", "name": "urgency", "options": [ { "label": "低 (1週間以上)", "value": "低 (1週間以上)" }, { "label": "中 (1週間以内)", "value": "中 (1週間以内)" }, { "label": "高 (3日以内)", "value": "高 (3日以内)" }, { "label": "緊急 (当日)", "value": "緊急 (当日)" }] }, { "type": "textarea", "label": "問い合わせ内容", "name": "question_content", "placeholder": "なにもしてないのにパソコンがこわれた" //"optional":"true" ←これを追加することで任意の項目になる(直前にカンマ追加も忘れないよう) } ]} ) } var options = { 'method' : 'POST', 'payload' : dialog } return options; } //----------------------------------------slackIDからメールアドレスを取得 function findUserBySlackID(slackID){ var url="https://slack.com/api/users.info" var payload = { "token" : bot_token, "user" : slackID } var options = { "method" : "GET", "payload" : payload, "headers": { "contentType": "x-www-form-urlencoded", } } var json_data = UrlFetchApp.fetch(url, options); var otp = json_data json_data = JSON.parse(json_data) if (json_data["ok"]){ var user_email = String(json_data["user"]["profile"]["email"]) } else { var user_email = "null" //slackユーザーが居てメールが無いことはないと思いますが念のため } return user_email } //----------------------------------------postMessage作成 function createMessage(message_data,bot_token,channel_id){ var payload = { "token" : bot_token, "channel" : channel_id, "text" : message_data } var options = { "method" : "POST", "payload" : payload, "headers": { "contentType": "x-www-form-urlencoded", } } return options; } //----------------------------------------Invite作成 function createInvite(user_id,bot_token,channel_id){ var payload = { "token" : bot_token, "channel" : channel_id, "users" : user_id } var options = { "method" : "POST", "payload" : payload, "headers": { "contentType": "x-www-form-urlencoded", } } return options; }
スクリプトプロパティの設定
Googleフォーム版と同様に設定してきます。
下記のパラメータを設定します。
sup_mem1
サポートメンバーのslackユーザーID(メンション通知用)
※ユーザーIDの調べ方は別途ググってくださいm(_ _)m
gss_id
スプレッドシートのID
bot_token
slackアプリのOAuth & Permissions → Bot User OAuth Access Token
channel_id
slackのヘルプデスク用チャンネルのID
verify_token
slackのアプリのBasic Information → Verification Token
Verification Tokenはこちら
functionの説明
doPost
メインの処理
createDialog
dialog.openメソッドに必要な中身を作る処理
メイン処理の中に入れるとハイパー見辛いので
findUserBySlackID
slackからユーザーリストを貰い、ユーザーIDを元にメールアドレスを探す処理
createMessage
chat.postMessageメソッドに必要な中身を作る処理
createInvite
channels.inviteメソッドに必要な中身を作る処理
権限のチェック
コードが完成したら、保存して再度公開しましょう。
その際に、新たに権限が必要なコードが追加されていた場合、許可を求められるので許可します。
実行テスト
スラッシュコマンドの実行
コマンド入力→送信
フォームが表示されます(`ェ´)ピャー
入力せずに送ろうとすると怒られます。
slackの通知
入力して送信します。
うまく投稿されました。
GASとslackでヘルプデスクフォームを作った話(スラッシュコマンド版 準備編)
前回の記事で紹介した、GASとslackでヘルプデスクフォームを作った話(Googleフォーム版)の起点を、
Googleフォームではなくスラッシュコマンドから行うパターンの作成方法の紹介です。
重複する内容が多くあるので、slackアプリ作成の部分等は前回の作業の差分のみ書いていきます。
今回も準備編とコード編に分けて書きます。
動作の流れ
- ユーザーがslackのスラッシュコマンドを送信
- slack上にフォームが表示され、そこからヘルプ内容を投稿
- フォームの内容を、スプレッドシートに記録
- 質問した人をslackの専用チャンネルへ招待する
- フォームの内容を、slackの専用チャンネルにポストして、情シスへメンションを送る
事前準備
GASの準備
新しいプロジェクトを作成し、いったん保存します。
GASはスタンドアロン型でOKです。
作成したGASをWebアプリケーションとして公開します。
公開設定等を聞かれるので、次のように設定します。
Project version → New
Excute the app as → Me
Who has access to the app → Anyone , even anonymous
解説
コードを更新するごとに、都度公開する必要があります。
その時に、2回目以降はProject versionを手動でNewにする必要があります。
Excute the app asは、アプリの実行者です。
Who has asccess to the appはアプリにアクセス可能な人の設定です。今回はslackがリクエスト元になるので、誰でもOK(非ログイン状態でもOK)にしています。
Current web app URLは後で使用するので控えておきます。
スプレッドシートの準備
slackアプリの準備
Googleフォーム版と同様に作成し、追加で必要な設定を書いていきます。
スラッシュコマンドの作成
実行に必要なコマンドを作成していきます。
左メニューの「Slash Commands」→「Create New Command」
スラッシュコマンドのパラメータを設定していきます。
必須
Command → 実行したいときに打つコマンド
Request URL → さっき公開した、GASのURL(Current web app URL)
任意
Short Description → コマンドの簡単な説明
Usage Hint → コマンド以外にパラメータを入力させるときのヒント
アプリを再インストールしてくださいと言われますが、一旦無視します。(もう1点設定を変更するため)
Interactivity & Shortcutsの設定
Interactivityは、フォーム(ダイアログ)やボタン等からリクエストがあった時にどのURLにリクエストするか設定するものです。
そのためスラッシュコマンドを受け取って実行するGASと、フォームやボタンから受けたリクエストを処理するGASを分けることもできます(たぶん)
左メニューの「Interactivity & Shortcuts」をクリックし、右上のツマミを「On」にします。
今回は1つのGASでスラッシュコマンドもフォームの処理も行うので、
Request URLにGASのURL(Current web app URL)を入れます。
設定が終わったのでアプリをワークスペースに再インストールします。
左メニューの「Install App」→「Reinstall App」
許可を求められるので、投稿先チャンネル(ヘルプデスク用のチャンネル)を指定し、許可します。
アプリの準備はこれで完了です。
slackチャンネルの準備
GASとslackでヘルプデスクフォームを作った話(Googleフォーム版 コード編)
GASとslackでヘルプデスクフォームを作った話(Googleフォーム版 準備編)の続きです。
コード全体
コメントアウトで説明を入れています。
function onFormSubmit(e){ FormApp.getActiveForm() //フォームを取得 //スクリプトのプロパティからトークンやIDを取得 var bot_token = PropertiesService.getScriptProperties().getProperty('bot_token') //slackアプリ OAuth & PermissionsのBot User OAuth Access Token var channel_id = PropertiesService.getScriptProperties().getProperty('channel_id') //#helpdeskのチャンネルID var gss_id = PropertiesService.getScriptProperties().getProperty('gss_id') //スプレッドシートのID var sup_mem1 = PropertiesService.getScriptProperties().getProperty('sup_mem1') //サポートメンバー1のslackUserID //メンバー数に応じて、必要があれば追加してください。(スクリプトプロパティも) //var sup_mem2 = PropertiesService.getScriptProperties().getProperty('sup_mem2'); //サポートメンバー2のslackUserID var time = e.response.getTimestamp() //投稿日時をフォームから取得 var email = e.response.getRespondentEmail() //メールアドレスの回答を取得 var form_items = e.response.getItemResponses() // 質問内容と回答が格納されています。 var q1 = form_items[0].getResponse() // 氏名の回答を取得 var q2 = form_items[1].getResponse() // 緊急度の回答を取得 var q3 = form_items[2].getResponse() // 詳細の回答を取得 //メールアドレスからslackUserIDを取得 var user_id = findUserByEmails(email,bot_token) //スプレッドシートへ記録 var sheet_name = "helplist" //スプレッドシートのシート名 var spread_sheet = SpreadsheetApp.openById(gss_id) var no = spread_sheet.getSheetByName(sheet_name).getLastRow() //最終行から管理Noを決定 spread_sheet.getSheetByName(sheet_name).appendRow( //最終行へ記入 [no,time,email,user_id,q1,q2,q3] ) //送信者のslackユーザーを招待、メンションの準備をする。居ない場合はメールアドレス if (user_id == "null"){ var applicant = email } else { var applicant = "<@" + user_id + ">" //送信者へメンションする書式 var created_invite = createInvite(user_id,bot_token,channel_id) //チャンネルへ招待 var response = UrlFetchApp.fetch("https://slack.com/api/conversations.invite", created_invite); } //チャンネルへ投稿 var message_data = "<@" + sup_mem1 + ">" // サポートメンバー増があれば必要に応じて追加<@" + sup_mem2 + ">" + "\n" + applicant + " から問い合わせがありました。" + "\n\n*No*\n" + no + "\n\n*氏名*\n" + q1 + "\n\n*緊急度*\n" + q2 + "\n\n*問い合わせ内容*\n" + q3 var created_message = createMessage(message_data,bot_token,channel_id) var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", created_message); } //---------------------------------------メールアドレスからslackIDを取得 function findUserByEmails(email,bot_token){ var url="https://slack.com/api/users.lookupByEmail" var payload = { "token" : bot_token, "email" : email } var options = { "method" : "GET", "payload" : payload, "headers": { "contentType": "x-www-form-urlencoded", } } var json_data = UrlFetchApp.fetch(url, options); json_data = JSON.parse(json_data) if (json_data["ok"]){ var user_id = String(json_data["user"]["id"]) } else { var user_id = "null" } return user_id } //---------------------------------------postMessage作成 function createMessage(message_data,bot_token,channel_id){ var payload = { "token" : bot_token, "channel" : channel_id, "text" : message_data } var options = { "method" : "POST", "payload" : payload, "headers": { "contentType": "x-www-form-urlencoded", } } return options; } //---------------------------------------invite作成 function createInvite(user_id,bot_token,channel_id){ var payload = { "token" : bot_token, "channel" : channel_id, "users" : user_id } var options = { "method" : "POST", "payload" : payload, "headers": { "contentType": "x-www-form-urlencoded", } } return options; }
スクリプトプロパティの設定
「ファイル」→「プロジェクトプロパティ」
「スクリプトのプロパティ」に準備編で作成したトークンやIDをセットします。
sup_mem1
サポートメンバーのslackユーザーID(メンション通知用)
※ユーザーIDの調べ方は別途ググってくださいm(_ _)m
gss_id
スプレッドシートのID
bot_token
slackアプリのOAuth & Permissions → Bot User OAuth Access Token
channel_id
slackのヘルプデスク用チャンネルのID
権限のチェック
GASの権限をチェックしたいので、一度実行します。
この画面が出た場合、権限が不足している場合があるので許可します。
フォームからpostされるはずのデータが無いので、許可後は必ずエラーが出ます。
終わりに
GASもslackアプリもまだまだ初心者なので、解釈が間違っている部分や効率化できる部分があると思います。
ここが間違っている、こうした方がもっと良い、といった点があればコメントいただけると嬉しいです!
GASとslackでヘルプデスクフォームを作った話(Googleフォーム版 準備編)
お久しぶりです。カインです。
初めて情シスらしい記事を書きます(笑)
GAS(Google Apps Script)とslackを使った社内用ヘルプデスクフォームを作ったので、その紹介です。
今回の記事はGoogleフォームを起点としたものです。
色々な記事を参考にしておりますので、類似点等があるかもしれませんがご容赦ください。
後日、slackのスラッシュコマンドを起点にしたものも書く予定です。
長いので、準備編とコード編に分けて書きます。
後半→GASとslackでヘルプデスクフォームを作った話(Googleフォーム版 コード編)
作った人のスペック
この程度の人間でも作れるよ!というアピール
- 情シス歴3年(公務員2年、民間1年)
- プログラミングの勉強経験はCの基礎的な部分のみ
- Excel関数、Access、VBA等を独学で勉強して業務改善をした程度
- GAS(JavaScript)は初心者
動作の流れ
- ユーザーがGoogleフォームからヘルプ内容を投稿
- フォームの内容を、スプレッドシートに記録
- 質問した人のメールアドレスからslackのユーザーを特定し、slackの専用チャンネルへ招待する
- フォームの内容を、slackの専用チャンネルにポストして、情シスへメンションを送る
必要なもの
事前準備
GASでコードを書いていく部分以外に必要なものを準備していきます。
GoogleフォームとGAS(コンテナバインド型)の準備
Googleフォームの作成
まずフォームを作成します。
今回は、
- メールアドレス
- 氏名
- 緊急度
- 詳細
の4項目でフォームを作成します。
メールアドレスの項目は、歯車アイコンをクリックして「メールアドレスを収集する」にチェックを入れて保存します。
今回は手動で入力させる形ですが、フォームの公開組織を限定することで、ログインユーザーから自動収集することもできます。
G Suiteアカウントに入れなくなった事態を想定して手動入力にしています。
項目を足していき、完成したフォームはこんな感じになりました。
GAS(コンテナバインド型)の作成
次に、このフォームに紐づいたコンテナバインド型のGASを作成します。
編集画面右上の「・・・」メニューから、「スクリプト エディタ」を開きます。
プロジェクト名を適当に決めて、いったん保存します。
トリガーの作成
フォーム送信時にこのGASが動作するように、トリガーを作成します。
先にfunction名を決める必要があるので、後で書く予定の「onFormSubmit」にしておきます。
「編集」→「現在のプロジェクトのトリガー」をクリックします。
ページ右下の「トリガーを追加」をクリックします。
イベントの種類を選択→フォーム送信時 に変更して保存します。
これで、フォームが送信されたときに「function onFromSubmit」が実行されるようになりました。
スプレッドシートの準備
記録用のスプレッドシートを作成しておきます。
スプレッドシートのIDを控えておきます。
スプレッドシートのIDは、URLのxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxの部分です。
https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit#gid=0
1行目はフィールド名を適当に入れておきましょう。
slackアプリの準備
まずはアプリを作成します。
slackメニュー→「設定と管理」→「アプリを管理する」
ページ上部の「ビルド」
ページ上部の「Your Apps」
「Create New App」
アプリの名前とインストール先のワークスペースを決定して、「Create App」
これでアプリは作成できたので、色々設定をいじっていきます。
Incoming Webhooks
botがチャンネルにデータを送信できるようにする設定です。
左メニューの「Incoming Webhooks」をクリックし、右上のツマミを「On」にします。
OAuth & Permissions
ここでは、アプリに持たせる権限を設定します。
今回のGoogleフォーム版では、
を付与したいです。
左メニューの「OAuth & Permissions」をクリックします。
Bot Token Scopesの「Add an OAuth Scope」をクリックして、
- chat:write
- users:read
- users:read.email
- channels:manage
下記も追加(2020/10/16追記)
- groups:write
- im:write
- mpim:write
を追加します。
ちなみに、どの権限を追加するかは、使用するAPIのメソッドによります。
今回はこの3つのメソッドを使います。
それぞれのメソッドのページに、「Works with」の部分に必要な権限(Required scope(s))が書いてあります。
https://api.slack.com/methods/chat.postMessage
https://api.slack.com/methods/users.list
https://api.slack.com/methods/channels.invite
Incoming WebhooksとOAuth & Permissionsの設定が済んだら、「Install App to Workspace」をクリックします。
Incoming Webhooksの投稿先を聞かれるので、作成したチャンネル(#helpdesk)を指定し、「許可する」をクリックします。
許可後にトークンが作成されるので、「Bot User OAuth Access Token」を控えておきます。
slackチャンネルの準備
専用のチャンネルを適当に作成して、チャンネルにアプリを追加します。
ここでは、チャンネル名は#helpdeskとしています。
先ほど作ったアプリを追加します。
このチャンネルのチャンネルIDを控えておきます。
チャンネルIDは、URLのYYYYYYYYYYYの部分です。
https://app.slack.com/client/XXXXXXXXX/YYYYYYYYYYY
コード編
グラボのコード43エラーでハマった話
お久しぶりです。カインです。
見事に三日坊主がキマって1ヶ月近く放置していました。
新型HHKBを購入して文字が打ちたくなったので投稿します。
私事ではありますが、2年ほど悩まされていた問題が解決されたので事例として紹介します。
よくある話でもないのでサクっと端折って書きます。
グラフィックボードのドライバでエラー発生
私の現在のメインPCは2016年6月頃に自作し、今も稼働しています。
とある日、次のようなエラーが発生しました。
問題が発生したのでこのデバイスは停止しました。(コード 43)
このエラーはドライバが異常だからグラボを正常に認識できないYO!というもので、画像処理にグラボが使用できません(たぶん)
手あたり次第解決策を試す
- Windows 10のダウングレード
1803から1709に戻すことで正常に認識するようになり、当面はこれで凌いできましたが、サポートも終了し騙し騙し使うのも難しくなってきたので重い腰を上げて、「nvidia code43」等のキーワードでググり、引っかかったものを手あたり次第試しました。
- DDUを使ってドライバを完全にアンインストールしてから再インストール
- 上記の手段で複数のバージョンのドライバを試行
- マザーボードのBIOS(UEFI)のアップデート
- BIOS(UEFI)の設定変更(オンボードグラフィックの無効化)
- VIRTU MVPのアンインストール(もともと入れていない)
- Windowsの初期化
どれを試してもだめでした。
文字で書くとこれだけのことしかしてませんが、再インストールや初期化はめちゃくちゃ時間がかかるので心が折れそうになります。
色々試したところ、Windows 10 1709、NVIDIA 382.05では正常に動作するようだが、1803に上げてもだめ、ドライバを変えてもダメでした。
解決に至った手段
諦めかけていたとき、グラボのBIOSのカスタマイズや、コード43エラーのフォーラムで電圧を変えたら直ったという情報を見つけました。
グラボのBIOSは自分では弄ってないけど、メーカーのカスタムや前オーナーが弄っている可能性があるかも…と思いダメ元でやってみました。
BIOSのデータを入手する
VGA Bios Collection | TechPowerUp
このサイトからグラボのBIOSを入手します。
何個かBIOSがありましたが、最もCore/Mem/Boostが低いものを選びました。
BIOSを上書きする
NVIDIA NVFlash Download | TechPowerUp
NVFlashというツールを使い、グラボのBIOSを書き換えます。
コマンドはこちらのサイトを参考にしました。
GeForce GTX 1080のVideo BIOS書き換え | PCパーツ海外情報
なお、nvflashのコマンドはnvflash64で実行しています。(64bitOSのため)
直りました!!!
半ば諦めていただけにめちゃくちゃ嬉しい!
Windows 1909を使える喜び!!
最新版を使用できる安心感は違いますね。
何が原因だったのか
解決した時点で満足してしまい、根拠を探していないので妄想です。
グラボはヤフオクで購入したものなので前所有者がカスタマイズしていたか、そもそもメーカーでOC仕様にチューニングされていた可能性があります。
何度も再インストールした時間を返してほしい/(^o^)\
ついでにGoogle Chromeが真っ黒になった
グラボが直ったと思ったらChromeが真っ黒になるようになってしまいました。
本当にただ真っ黒なだけだったのでSSはありません。
%USERPROFILE%\AppData\Local\Google\Chrome\User Data\ShaderCache
を削除する
ざっくり結論から言うと、上記の方法で解決しました。
グラボのBIOSが変わったから別物として認識されたのかな?わかりません。
Chromeのショートカットのリンク先に「--disable-direct-composition」を追加する方法や、ハードウェアアクセラレーションを切る対処方もありましたが、どちらもその場しのぎでしかなく、Google Mapの3Dビューが使えなくなったので論外でした。
今回の件で得た教訓
新品を買おう。