SlackとLINEを連携する(2) LINE→Slack
LINEに投稿されたメッセージをSlackにも通知するために、Google Apps Script(以下GAS)で処理します。
同時に、SlackからLINEへ通知する際に必要となるLINEユーザ情報をファイルに貯め込むことも必要です。
GASもjavascriptもほぼ初めてなので、妙な部分があったら優しく教えてください。
長いので要点だけ解説します。
ソースコード
/** * @fileoverview LINEボットに対して送られたメッセージをSlackのgeneralチャンネルに転送する。 * @author smp */ // LINEアクセストークン var LINE_ACCESS_TOKEN = '(秘密)' // Slack Webhook 用URL var SLACK_URL = 'https://hooks.slack.com/services/(秘密)'; // GoogleドライブディレクトリID var GOOGLE_DRIVE_DIR_ID = '(秘密)'; // LINE BOTフォロー時リプライメッセージ var LINE_FOLLOW_REPLY_MESSAGE = 'LINE Slack連携ユーザに登録しました。Slackの投稿がLINEに届き、LINEの投稿がSlackに届くようになります。'; // LINE返信用APIのURL var LINE_MESSAGE_REPLY_URL = "https://api.line.me/v2/bot/message/reply"; // LINEプロフィール取得APIのURL var LINE_PROFILE_URL = 'https://api.line.me/v2/bot/profile'; // LINEユーザ一覧ファイル名 var LINE_USER_LIST_FILENAME = 'LineUserList.json'; /** * @summary Postリクエスト処理。 * LINEボットからPOSTで通知されたタイミングで実行する関数。 * messageイベントの場合は、イベントに含まれるチャンネル名、ユーザ名、本文をSlackに通知する。 * followイベントの場合は、ユーザリストにユーザ情報を追加する。 * leaveイベントの場合は、ユーザリストからユーザ情報を削除する。 * @param e LINEから通知されるメッセージ */ function doPost(e) { // 各種情報を取得 var jsonData = JSON.parse(e.postData.contents); var event = jsonData.events[0]; try { switch (event.type) { case 'message': // メッセージ投稿時 switch (event.message.type) { case 'text': // ユーザーのメッセージを取得 // (1)返信用トークン var replyToken = event.replyToken; // (2)ユーザメッセージ var userMessage = event.message.text; // (3)ユーザID var userId = event.source.userId; // (4)タイムスタンプ var timestamp = event.timestamp; // ユーザIDからユーザ名を取得する。 var userName = getUserName(userId); // Slackにメッセージを送信する。 pushToSlack(userName, userMessage); break; default: // do nothing break; } break; case 'follow': // 友だち追加時 // (1)返信用トークン var replyToken = event.replyToken; // (2)タイムスタンプ var timestamp = event.timestamp; // (3)ユーザID var userId = event.source.userId; // ユーザIDからユーザ名を取得する。 var userName = getUserName(userId); // LINEユーザ一覧ファイルへユーザIDを追加する。 var addResult = addLineUserId(userId, userName, timestamp); if (addResult) { // 追加成功時、LINE BOTから返信メッセージを送る。 reply(LINE_FOLLOW_REPLY_MESSAGE, replyToken); } break; case 'unfollow': // ブロック時 // (1)タイムスタンプ var timestamp = event.timestamp; // (2)ユーザID var userId = event.source.userId; // ユーザIDからユーザ名を取得する。 var userName = getUserName(userId); // LINEユーザ一覧ファイルからユーザIDを削除する。 removeLineUserId(userId); break; default: // do nothing } } catch (err) { console.log(err); throw err; } } /** * @name LINEユーザ追加 * @summary LINEユーザ一覧ファイルにユーザ情報を追加する。 * @param {string} userId ユーザID * @param {string} userName ユーザ名 * @param {number} timestamp 登録日時( * @returns {boolean} 登録したか(true: 登録した、false: 登録しなかった) */ function addLineUserId(userId, userName, timestamp) { // 変数初期化 // (1)ユーザ一覧 var userList = []; // (2)保存するjsonデータを読み込み var jsonData = readJson(LINE_USER_LIST_FILENAME) // (3)新規登録ユーザ情報 var newUserData = { 'userId': userId, 'userName': userName, 'timestamp': timestamp } // 引数チェック if (userId == '') { return false; } // ユーザ一覧作成 // (1)既存ユーザ追加 if (jsonData.list) { for (var i in jsonData.list) { userList.push(jsonData.list[i]); // 新規ユーザと同じユーザIDが既に登録されていた場合は // ユーザを登録せずに終了する。 if (userId == jsonData.list[i].userId) { return false; } } } // (2)新規ユーザ追加 userList.push(newUserData); // 書き込むデータ生成 var newJsonData = { list: userList } // jsonデータ書き込み writeJson(LINE_USER_LIST_FILENAME, newJsonData); return true; } /** * @name LINEユーザ削除 * @summary LINEユーザ一覧ファイルからユーザ情報を削除する。 * @param {string} userId ユーザID * @returns {boolean} 削除したか(true: 削除した、false: 削除しなかった) */ function removeLineUserId(userId) { // 変数初期化 // (1)ユーザ一覧 var userList = []; // (2)保存するjsonデータを読み込み var jsonData = readJson(LINE_USER_LIST_FILENAME) // (3)ユーザを削除したか var removedFlg = false; // ユーザ一覧作成 if (jsonData.list) { for (var i in jsonData.list) { // ユーザIDが削除対象と異なる場合のみ、userListに追加 if (userId != jsonData.list[i].userId) { userList.push(jsonData.list[i]); } else { // 削除対象のユーザIDがユーザ一覧ファイルに存在した場合、 // 削除フラグをtrueにする。 removedFlg = true; } } } // ループ内で削除が一度も行われなかった場合、 // false(削除しなかった)をリターンして処理終了。 if (!removedFlg) { return false; } // 書き込むデータ生成 var newJsonData = { list: userList } // jsonデータ保存 writeJson(LINE_USER_LIST_FILENAME, newJsonData); return true; } /** * @name Google Drive内ファイル取得 * @summary Google Driveのディレクトリパスとファイル名から、ファイルを取得する。 * 存在しない場合は新しく作成する。 * @param {string} dirId ディレクトリID * @param {string} fileName ファイル名 * @returns ファイルオブジェクト */ function getGoogleDriveFile(dirId, fileName) { var dir = DriveApp.getFolderById(dirId); var file = dir.getFilesByName(fileName); if (file.hasNext()) { // 存在する場合 return file.next(); } else { // 存在しない場合 return dir.createFile(fileName, ''); } } /** * @name jsonファイル書き込み * @summary jsonファイルをGoogle Driveに書き込む。 * @param {string} jsonPath Google Drive内jsonファイルパス * @param {object} jsonData jsonデータ */ function writeJson(jsonPath, jsonData) { var content = JSON.stringify(jsonData); var file = getGoogleDriveFile(GOOGLE_DRIVE_DIR_ID, jsonPath); file.setContent(content); } /** * @name jsonファイル読み込み * @summary jsonファイルをGoogle Driveから読み込む。 * jsonファイルを読み込み、パースして返す。 * jsonファイルがない場合、新しくjsonファイルを作成する。 * @param {string} jsonPath Google Drive内jsonファイルパス * @returns {object} 読み込んだjsonデータ */ function readJson(jsonPath) { var file = getGoogleDriveFile(GOOGLE_DRIVE_DIR_ID, jsonPath); var content = file.getBlob().getDataAsString(); if (!content) { // ファイルの内容が空の場合 return {}; } else { var json = JSON.parse(content); return json; } } /** * @name LINEユーザ名取得 * @summary LINEユーザIDからLINEユーザ名を取得する。 * @description LINEユーザIDからLINEユーザ名を取得する。 * @param {string} userId LINEユーザID * @returns {string} LINEユーザ名 */ function getUserName(userId) { try { var url = LINE_PROFILE_URL + '/' + userId; var response = UrlFetchApp.fetch(url, { 'headers': { 'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN } }); return JSON.parse(response.getContentText()).displayName; } catch (err) { // LINE APIのエラー発生時 console.log(err); return ''; } } /** * @name Slackにメッセージ通知 * @summary Slackにメッセージを送る。 * @param {string} userName Slackに表示されるユーザ名 * @param {string} text Slackに表示される本文 * @param {string} channel チャンネル名 先頭の#は不要 * @param {string} emojiIcon 絵文字アイコン名 例:':chart_with_upwards_trend:' * @returns fetchの結果 */ function pushToSlack(userName, text, channel, emojiIcon) { // payload作成 var payload = { 'username': 'Lineからの通知 ' + userName, 'text': text, }; if (channel) { payload['channel'] = '#' + channel; } if (emojiIcon) { payload['icon_emoji'] = emojiIcon; } var options = { 'method': 'post', 'contentType': 'application/json', 'payload': JSON.stringify(payload), }; // メッセージ送信 var response = UrlFetchApp.fetch(SLACK_URL, options); return response; } /** * @name LINE BOT返信 * @summary LINE BOTへの投稿に対して、LINE BOTに返信させる。 * @param {string} text 返信するメッセージ * @param {string} replayToken LINEから通知される返信用トークン * @returns fetchの結果 */ function reply(text, replyToken) { // ヘッダデータ作成。 var headers = { "Content-Type": "application/json; charset=UTF-8", 'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN, }; // POSTリクエストデータ作成。 var postData = { "replyToken": replyToken, "messages": [ { 'type': 'text', 'text': text, } ] }; // 送信データを作成。 var options = { "method": "post", "headers": headers, "payload": JSON.stringify(postData) }; var response = UrlFetchApp.fetch(LINE_MESSAGE_REPLY_URL, options); return response; }
Postリクエスト受信と制御
doPost関数は特殊な関数で、GASをウェブアプリケーションとして公開した際に発行されるURLへPostリクエストを発行すると実行されます。
引数eにはLINEのアクション情報がjson形式で格納されています。詳しくはLINE Messaging APIのマニュアルを参照してください。
https://developers.line.biz/ja/reference/messaging-api/
e.postData.contentsをJSON.parseでパースして、イベント情報を取り出します。 全体の構成は以下のようになります。
function doPost(e) { // 各種情報を取得 var jsonData = JSON.parse(e.postData.contents); var event = jsonData.events[0]; try { switch (event.type) { case 'message': // メッセージ投稿時 switch (event.message.type) { case 'text': ... default: // do nothing break; } break; case 'follow': ... break; case 'unfollow': ... break; default: // do nothing } } ... }
switch caseで、event.typeの内容により場合分けをします。
# | event.type | LINEでの操作 | GASで実装する処理 |
---|---|---|---|
1 | message | BOTにメッセージ投稿 | Slack Incomming Webhook に対してメッセージ送信 |
2 | follow | BOTを友だち追加/ブロック解除 | LINEユーザ一覧ファイルにユーザ情報を追加 |
3 | unfollow | BOTをブロック | LINEユーザ一覧ファイルからユーザ情報を削除 |
Slackへ通知
pushToSlack
関数で実装してあります。
引数のchannelとemojiIconは設定できるので一応つけてますが、今回は使用していません。
function pushToSlack(userName, text, channel, emojiIcon) { // payload作成 var payload = { 'username': 'Lineからの通知 ' + userName, 'text': text, }; if (channel) { payload['channel'] = '#' + channel; } if (emojiIcon) { payload['icon_emoji'] = emojiIcon; } var options = { 'method': 'post', 'contentType': 'application/json', 'payload': JSON.stringify(payload), }; // メッセージ送信 var response = UrlFetchApp.fetch(SLACK_URL, options); return response; }
定数SLACK_URL
には、Slack Incomming Webhookを作成した際のURLを設定しておいてください。
主に実施していることはoptions
を作ることです。
method
どcontentType
は固定です。
payload
にはJson形式でSlackに投稿するメッセージを設定します。username
とtext
があればいいと思います。
仕様はSlackのSlack Incomming Webhookの設定画面に記載されています。
LINEユーザ一覧ファイルにユーザ追加
主にaddLineUserId
関数で実装しています。
LINEユーザ一覧ファイル(LineUserList.json)は以下のようなフォーマットにしました。 LINEユーザIDの一覧さえあればどのようなフォーマットでもいいです。
{ "list":[ { "userId":"Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "userName":"太郎", "timestamp":1561459938495 }, { "userId":"Uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy", "userName":"次郎", "timestamp":1561460491870 } ] }
addLineUserId関数でユーザ情報を追加します。 1. readJson関数でLINEユーザ一覧ファイルから既存ユーザ情報(jsonData)を読み込み 1. 既存ユーザ情報(jsonData)と新規登録ユーザ情報(newUserData)を加えた新ユーザ情報(newJsonData)を作成 1. writeJson関数で新ユーザ情報(newJsonData)をLINEユーザ一覧ファイルに書き込み
function addLineUserId(userId, userName, timestamp) { // 変数初期化 // (1)ユーザ一覧 var userList = []; // (2)保存するjsonデータを読み込み var jsonData = readJson(LINE_USER_LIST_FILENAME) // (3)新規登録ユーザ情報 var newUserData = { 'userId': userId, 'userName': userName, 'timestamp': timestamp } // 引数チェック if (userId == '') { return false; } // ユーザ一覧作成 // (1)既存ユーザ追加 if (jsonData.list) { for (var i in jsonData.list) { userList.push(jsonData.list[i]); // 新規ユーザと同じユーザIDが既に登録されていた場合は // ユーザを登録せずに終了する。 if (userId == jsonData.list[i].userId) { return false; } } } // (2)新規ユーザ追加 userList.push(newUserData); // 書き込むデータ生成 var newJsonData = { list: userList } // jsonデータ書き込み writeJson(LINE_USER_LIST_FILENAME, newJsonData); return true; }
LINEユーザ一覧ファイルからユーザ削除
主にremoveLineUserId
関数で実装しています。
addLineUserId
とほぼ同じ処理になります。
ユーザ一覧作成時に、引数のuserId
と同一のIDが存在した場合は追加処理をスキップします。
function removeLineUserId(userId) { // 変数初期化 // (1)ユーザ一覧 var userList = []; // (2)保存するjsonデータを読み込み var jsonData = readJson(LINE_USER_LIST_FILENAME) // (3)ユーザを削除したか var removedFlg = false; // ユーザ一覧作成 if (jsonData.list) { for (var i in jsonData.list) { // ユーザIDが削除対象と異なる場合のみ、userListに追加 if (userId != jsonData.list[i].userId) { userList.push(jsonData.list[i]); } else { // 削除対象のユーザIDがユーザ一覧ファイルに存在した場合、 // 削除フラグをtrueにする。 removedFlg = true; } } } // ループ内で削除が一度も行われなかった場合、 // false(削除しなかった)をリターンして処理終了。 if (!removedFlg) { return false; } // 書き込むデータ生成 var newJsonData = { list: userList } // jsonデータ保存 writeJson(LINE_USER_LIST_FILENAME, newJsonData); return true; }
確認
先にLINEの設定をしておきます。
メニューバーから「公開」「ウェブアプリケーションとして導入」を選択し、ウェブアプリケーションとして公開します。
LINE Developersの設定を開き、「メッセージ送受信設定/Webhook URL」にウェブアプリケーションのURLを設定しておきます。
あとはLINE BOTをフォローして、メッセージを通知してSlackに表示されればOKです。
手こずったこと
- GAS(javascript)ではswitch文でcaseのたびにbreakが必要。慣れてる人にとっては普通でしょうが、最近のプログラミング言語では珍しくなりましたね。ハマりました。
- ログが確認しづらくデバッグに手間取りました。console.log()で画面から確認できるものの、画面に反映されるまでに時間がかかる…?ような気がします。
- デバッグ作業でSlackに投稿されてしまいました(すぐ消しました)。Slackにテスト用チャンネルを用意して、SLACK_URLを切り替えることで対処しました。
まだ続きます
SlackとLINEを連携する(1) 概要と準備
Slack中心でコミュニケーションをしていますが、LINEでしか見られない人のためにSlackとLINEを連携してみます。
Google Apps Script、SlackのWebhook系のアプリ、LINE Messaging APIあたりを使います。
長くなったので数回に分けます。
実現したいこと
以下2項目を実現します。
- LINEにメッセージを投稿すると、Slackのgeneralチャンネルに通知される。
- Slackのgeneralチャンネルにメッセージを投稿すると、LINEに通知される。
機能概要
LINEからSlackへの通知
LINE BOTをフォローしてもらい、LINE BOTへメッセージを送ることでSlackにメッセージを通知できるようにします。
メインの処理はGoogle Apps Scriptで実現します。
概要図を示します。
Google Apps ScriptのdoPost関数では、(2)PostリクエストはLINEで行ったアクション種別が含まれており、それにより場合分けをします。
- LINE BOTがフォローされたら、LINE IDリストにIDを追加する。(3)(4)
- LINE BOTにメッセージが投稿されたら、Slack Outgoing WebhookへPostリクエストを送る。(5)
- LINE BOTがブロックされたら、LINE IDリストからIDを削除する。(3)(4)
ここでLINEユーザIDリストファイルが登場しますが、このファイルはSlackからLINEに通知する際に必要になります。
LINEへの通知はユーザ単位に送る必要があるため、どこかにID一覧を保存しておかなければなりません。
SlackからLINEへの通知
Slackのgeneralチャンネルに投稿したメッセージをSlack Outgoing WebhookでGoogle Apps Scriptに連携し、LINE IDリストにあるユーザに向けてPostリクエストを送信します。
メッセージを受けるLINEユーザは、あらかじめLINE BOTをフォローしておく必要があります。
メインの処理は先程と同じくGoogle Apps Scriptで実現します。
概要図を示します。
準備
以下のアカウントを取得します。また、必要情報を控えます。
各種アカウントの作り方はマニュアルや解説サイトがありますので省略します。
Googleアカウント
通知処理するためにGoogle Apps Script、設定ファイルとログの保存のためにGoogle Driveを使います。
プログラム・設定ファイルを格納するために、Google Driveに1つフォルダを作成してフォルダIDを控えてください。
フォルダIDはGoogle Driveで対象フォルダを開くとURLに表示されます。
https://drive.google.com/drive/u/0/folders/(ここにフォルダID)?ths=true
LINEアカウント
当然必要になります。
LINE Developers
LINEへの通知のためにLINE DevelopersのMessaging API(LINE BOTを作るための機能)を使用します。
https://developers.line.biz/ja/
LINEアカウントでログインできます。
プロバイダ、およびチャネルを作成してください。チャネルを作る際は「Messaging API」を選択してください。
「チャネル基本設定」タブで以下の設定をします。
- メッセージ送受信設定/Webhook送信
「利用する」を設定します。 - メッセージ送受信設定/Webhook URL
Google Apps Scriptをウェブアプリケーションとして公開した際に発行されるURLを指定します。あとで設定します。 - LINE@機能の利用/自動応答メッセージ
「利用しない」を設定します。 - LINE@機能の利用/友だち追加時あいさつ
「利用しない」を設定します。
LINEのアクセストークンを控えてください。長いです。
「アクセストークン(ロングターム)」と表示されています。
また、「Bot情報/LINEアプリへのQRコード」に表示されているQRコードをどこかに保存しておいてください。 後ほどLINEアプリでスキャンし、LINE BOTを友達に追加します。(今はまだ追加しない)
Slackアカウント
当然必要になります。
generalチャンネルにアプリ(カスタムインテグレーション)「Slack Outgoing Webhook」と「Slack Incoming Webhook」をインストールします。
Slack Outgoing Webhook
以下の項目を設定します。
- チャンネル
LINEに通知するSlackのチャンネルを選びます。ここでは「#general」としておきます。 - URL
Google Apps Scriptをウェブアプリケーションとして公開した際に発行されるURLを指定します。 あとで設定します。
以下の項目を控えておきます。
- トークン
アプリのIDです。不正な投稿を区別するためにこのIDとメッセージ内のIDが一致するか検証します。
Slack Incoming Webhook
以下の項目を設定します。
- チャンネルへの投稿
LINEメッセージを通知するチャンネルを選びます。ここでは#general
としておきます。
以下の項目を控えておきます。
- Webhook URL 長いです。
続く
アマチュアコンピュータ倶楽部でSlackを使ってみた
こんにちは。smpです。 クラブメンバーが皆社会人ということで、全員が集まれる機会が少ない、ということでコミュニケーションツールを採用しています。メールやLINEでも良かったのですが、本クラブはコンピュータを学びたい人が集まる集団。業界で人気らしい「Slack」を使ってみました。
Slackとは
ビジネス向けのチャットサービスです。アプリを導入したりほかサービスと連携させたりすることで、応用できることが特徴です。 類似のシステムにはChatworkやMicrosoft Teamがあります。 Slackはビジネス向けチャットの先駆けで、ネット上に多くの情報が公開されているので選びました。
ちょっと使ってみて分かった良い点
慣れたチャット形式
LINEなどで最近は皆チャットに慣れています。 Slackも基本は他のチャットと同じような操作で使用できるので、すんなりと使用できます。
様々なアプリが使用できる
世界中でアプリが開発されており、機能追加できます。 現在、アンケートのために「Polly」、乱数を発生させるために「Dice」をインストールしています。1
他システムから連携できる
様々なシステムから情報を連携してSlackで表示できます。また、Webhookを受け取れるので、自作アプリから投稿、なんてことも可能です。 現在は、サーバのバックアップイベントの通知と、倶楽部用のメールの通知をしています。
ちょっと使ってみて分かった問題点
招待からログインまでのハードル
ビジネス向けツールということで、アカウント管理がしっかりしています。 その分、招待されてアカウント作ってワークスペースにログイン、というステップを踏まなければならず、 慣れない人にとってはハードルが高いようです。 一度設定してしまえばそれほど難しくないので、みんなで集まった際に登録するのがいいと思います。
LINEでいいんじゃない?と言われる
「LINEでもいいんです。でも知的好奇心がSlackを使えと言うんです!」
LINEじゃないと通知見ません
LINEへ通知できるようにしました。(後日記事を書く予定)
まとめ
アマチュアコンピュータクラブでも導入さえしっかり行えば十分使用できると思います。
アプリや連携はまだまだ研究中です。Raspberry PIと組み合わせてIoTと組み合わせたりとかも面白そうですね。
でも「LINEでいいんじゃない?」という意見は否定できない...
-
定例会の日程決めで使用しました。Pollyでアンケートを取り参加人数の多い日を選ぶつもりでしたが、同数になったのでDiceで公平に決めました。↩
2019年06月定例会 Scratch体験
こんにちは。smpです。 6/8(土)に第2回となる定例会を開催しました。 参加者は5人でした。
Scratch体験会
今回からはなにかテーマを設けて、勉強していきます。
取り上げたのはグラフィカルなプログラミング言語「Scratch」。
子供や初心者でも簡単にプログラムを作ることができるらしく、しばしば子供向けにNHKの番組や習い事でも扱われるそうです。
参加者の1人のお子さんは、Scratchを使ってゲームのようなものを作れるのだとか。
子供たちがガンガンプログラミングを勉強していく中、社会人である私達が遅れを取ってはなりませんね。
本日は体験ということで、まずはチュートリアルをやってみて、各自好きなように遊んでもらいました。
ここからアクセスできます。:https://scratch.mit.edu/
参加者の皆様は、ExcelのVBAなら勉強したことがあるけど他のプログラミング言語は初めて、という方ばかり。
それでもScratchはブロックを組み合わせてプログラミングできるので、カンタンに楽しくアニメーションを含めたプログラムを作ることができました。
猫のキャラクターが壁にぶつかるとニャーと鳴いたり、突然拡大してみたり、なかなか面白いプログラムを作っていました。
Scratch体験会で学んだこと
Scratchを使ってみて、皆様からは以下のような意見をいただきました。
- ブロックを配置するだけでいいので使いやすい・わかりやすい。
- 操作は違うが、ループの作り方はVBAと同じ。
- 「キャラクタを10歩動かす」というのはVBAでは大変だが、Scratchだと1つの命令で実現できる。
見た目は違いますが、作りは他のプログラミングと同じく、基本構造(逐次、分岐、繰り返し)からなっています。
gotoなどが無い分、よりきれいな構造を作りやすいかもしれません。
また、プログラムによる自動化の魅力「繰り返し」ですが、他のプログラミング言語だと初心者泣かせの「変数」を使わなければなりません。
Scratchだとキャラクタの位置などの基本的な状態はScratch側で管理してくれるので、変数なしで繰り返しが作れます。敷居が低い1つの理由ですね。
次回もなにかテーマを設けて、勉強していきたいと思います。 本クラブ活動に興味がありましたらぜひご連絡ください。
はじめましてとキックオフ会
はじめまして!smpと申します。 わたしたちは富山県で活動する社会人サークルです。 2019年5月18日にキックオフ会ということで1時間程度の集会を行いましたので、その様子をご紹介します。
現在メンバー全員で9人、うち6人が参加しました。 このクラブは各自自由に目標を立てるので、集会ではそれぞれのやりたいことを発表していただきました。 パソコンは詳しくないので1から学習したい、子供がいるので学校での情報教育について勉強したい、 資格勉強をしたい、ドローンを使ってみたい、などなど。
今回はほぼ顔合わせのみでしたが、次回からはなにか成果のある活動をしていきたいです。