WebAPIが提供する機能
スマホアプリから送られてくる処方情報をGoogleカレンダーに書き込む機能を提供するWebAPI。
【追加】
スマホアプリから送られてくる設定情報をGoogle spreadsheetに書き込む機能と読み出す機能を追加。
構築環境
GAS (Google Apps Script) を利用して開発。
doPostによるPOSTメソッドによるWebAPIの提供
インターフェース
入力データ(スマホ → Webサービス)
{
"parameter": {},
"contextPath": "",
"contentLength": 589,
"queryString": "",
"parameters": {},
"postData": {
"type": "application/json",
"length": 589,
"contents": "{\"drugs\":[\"JAHISTC04,1\",\"1,山下 浩介,1,19701206,*******,岡山県**市***丁目*-*,***-**-****,,,,ヤマシタ コウスケ\",\"2,1,********アレルギー2,1\",\"2,9,********禁忌2,1\",\"5,20190426,1\",\"11,医療法人 オルカ医院,13,1,1234567,1130021,東京都文京区本駒込2-28-16,03-3946-0001,1\",\"15,森久 諒子,03-3946-0001,1\",\"201,1,クラビット細粒10% 100mg(レボフロキサシンとして),3,g,2,621925901,1\",\"301,1,【1日3回毎食後に】,3,日分,1,1,,1\",\"\"]}",
"name": "postData"
}
}
リスト1.WebAPIの入力データ
postDataのcontentsにJSON形式の処方情報が設定されて送られてくる。
この情報は単なる文字列なので、JSON.parse()関数を使ってjavascriptオブジェクトに変換する。
var contents = JSON.parse(e.postData.contents);変換されたjavascriptオブジェクトは次のようになる。
{
"drugs": [
"1,鈴木 太郎,1,S330303,,東京都港区新橋1丁目,03-1234-1234,,B+,63.7,スズキ タロウ",
"2,1,乳製品,1",
"2,2,セフェム系(発熱),1",
"5,20190808,1",
"11,医療法人 オルカ医院,13,1,1234567,1130021,東京都文京区本駒込28-26,03-3946-0001,1",
"15,工業会 次郎,03-4567-4567,1",
"201,1,ノルバスク錠2.5mg,1,錠,1,,1",
"201,1,クラビット細粒10% 100mg(レボフロキシンとして),3,g,2,621925901,1",
"301,1,【1日3回毎食後に】,1,日分,1,1,,1",
"201,1,ペンニードル30Gテーパー,14,本,1,,1",
"201,1,バイアグラ,1,錠,1,,1",
"301,1,毎食後服用,3,日分,1,1,,1"
],
"settings": {
"morning": {"hours": 7, "minutes": 00},
"lunch": {"hours": 12, "minutes": 00},
"dinner": {"hours": 18, "minutes": 00},
"sleep": {"hours": 23, "minutes": 00},
}
}
リスト2.postDataのcontentsをjavascriptオブジェクトに変換したもの
ここで、drugsは処方情報、settingsは設定情報である。両者は排他的で、用途に応じてどちらか一方だけが設定される。
出力データ(Webサービス → スマホ)
{ "status": "0" }
リスト3.WebAPIからの出力
プログラム
GASでWebAPIを作成する場合、GETメソッドならdoGet、POSTメソッドならdoPost関数でエントリポイントを実装する。
doPost関数の引数eの中にリスト1に示す情報がオブジェクト形式で入っている。
function doPost(e){ //このeの中にPOSTされてきた情報が入っている(オブジェクト形式)
var results = { "status": "0" };
var logs = [];
debug(JSON.stringify(e));
try {
var contents = JSON.parse(e.postData.contents);
debug(JSON.stringify(contents, null, ' '));
if (contents.drugs) {
// 処方情報
var prescription = barcode2json(contents.drugs);
debug(JSON.stringify(prescription, null, ' '));
for(var i = 0; i < prescription.dispensing[0].RPs.length; i++) {
var RP = prescription.dispensing[0].RPs[i];
debug(JSON.stringify(RP, null, ' '));
youho_hyouzi(RP);
}
} else if (contents.settings) {
// 設定情報
debug(JSON.stringify(contents.settings, null, ' '));
setSettings(contents.settings);
} else {
throw new Error('e.postData.contentsにdrugsもsettingsもありません。');
}
} catch(err) {
logs.push(err);
results.logs = logs.join('\n');
}
debug(JSON.stringify(results));
// 戻り値作成
var output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.JSON);
output.setContent(JSON.stringify(results));
return output;
}
リスト4.エントリーポイント(doPost関数)
まず、入力データからリスト2に示す処方情報をJSON.parse()関数でJSON形式に変換し、それを以下に示すbarcode2json()関数でリスト6に示すフォーマットに変換する。
function barcode2json(lines) {
var prescription = {
patientInfo: {
No1: {},
No2: []
},
dispensing: [],
};
var dispensing = {
institution: {
No5: {},
No11: {},
No15: {},
},
RPs: [],
};
var RP = {
drugs: [],
dosage: '',
};
lines.forEach(function(line){
var items = line.split(',');
switch(items[0]) {
// 患者基本情報(レコードNo.1 患者情報レコード)
case "1":
prescription.patientInfo.No1 = {
name: items[1],
gender: items[2],
birthdate: items[3],
zipcode: items[4],
address: items[5],
phone: items[6],
emergencyContact: items[7],
bloodType: items[8],
weight: items[9],
kana: items[10],
};
break;
// 基本情報(レコードNo.2 患者特記レコード)
case "2":
prescription.patientInfo.No2.push({
type: items[1],
content: items[2],
creator: items[3],
});
break;
// 調剤-医療機関情報(レコードNo.5 調剤等年月日レコード)
case "5":
dispensing.institution.No5 = {
date: items[1],
creator: items[2],
};
break;
// 調剤-医療機関情報(レコードNo.11 調剤-医療機関等レコード)
case "11":
dispensing.institution.No11 = {
name: items[1],
prefCode: items[2],
tensuCode: items[3],
code: items[4],
zipCode: items[5],
address: items[6],
phone: items[7],
creator: items[8],
};
break;
// 調剤-医療機関情報(レコードNo.15 調剤-医師・薬剤師レコード)
case "15":
dispensing.institution.No15 = {
name: items[1],
contact: items[2],
creator: items[1],
};
break;
// 薬品情報(レコードNo.201 薬品レコード)
case "201":
RP.drugs.push({
No201: {
rp: items[1],
name: items[2],
dose: items[3],
unit: items[4],
codeType: items[5],
code: items[6],
creator: items[7],
}
});
break;
// 用法情報(レコードNo.301 用法レコード)
case "301":
RP.dosage = {
No301: {
rp: items[1],
name: items[2],
quantity: items[3],
unit: items[4],
formCode: items[5],
dosageType: items[6],
dosageCode: items[7],
creator: items[8],
}
};
dispensing.RPs.push(RP);
RP = {
drugs: [],
dosage: '',
};
break;
default:
console.log('default:' + items[0]);
break;
}
});
prescription.dispensing.push(dispensing);
return prescription;
}
リスト5.処方データをJSON形式に変換する(barcode2json)
この関数によってリスト2のデータが下記のJSON形式のオブジェクトに変換される。
{
"patientInfo": {
"No1": {
"name": "山下 浩介",
"gender": "1",
"birthdate": "19701206",
"zipcode": "*******",
"address": "岡山県**市***丁目*−*",
"phone": "***-**-****",
"emergencyContact": "",
"bloodType": "",
"weight": "",
"kana": "ヤマシタ コウスケ"
},
"No2": [
{
"type": "1",
"content": "********アレルギー2",
"creator": "1"
},
{
"type": "9",
"content": "********禁忌2",
"creator": "1"
}
]
},
"dispensing": [
{
"institution": {
"No5": {
"date": "20190426",
"creator": "1"
},
"No11": {
"name": "医療法人 オルカ医院",
"prefCode": "13",
"tensuCode": "1",
"code": "1234567",
"zipCode": "1130021",
"address": "東京都文京区本駒込2−28−16",
"phone": "03-3946-0001",
"creator": "1"
},
"No15": {
"name": "森久 諒子",
"contact": "03-3946-0001",
"creator": "森久 諒子"
}
},
"RPs": [
{
"drugs": [
{
"No201": {
"rp": "1",
"name": "クラビット細粒10% 100mg(レボフロキサシンとして)",
"dose": "3",
"unit": "g",
"codeType": "2",
"code": "621925901",
"creator": "1"
}
}
],
"dosage": {
"No301": {
"rp": "1",
"name": "【1日3回毎食後に】",
"quantity": "1",
"unit": "日分",
"formCode": "1",
"dosageType": "1",
"dosageCode": "",
"creator": "1"
}
}
}
]
}
]
}
リスト6.関数barcode2jsonの出力
剤(PR)単位でカレンダーに服薬予定を書き込むのが次に示すyouho_hyouzi関数である。 引数PRはリスト6のPRs配列の要素が一つずつ順番に渡される。
function youho_hyouzi(RP){
// 用法名称
var dose = dosage_table[RP.dosage.No301.name];
// 服薬タイミング
var timing = ["朝", "昼", "夜", "寝"];
// 食前か食後か?
var before_after = dose["食"];
// 服薬タイミング(take_timing)ごとに服用の有無(doesTake)と食前か食後の区分を表示する
for(var i = 0; i < timing.length; i++) {
var take_timing = timing[i];
//take_timing =朝昼夜寝(0123)
//doesTake=薬があるかないか
//before_after=食前か後か
var doesTake = dose[take_timing];
if(doesTake == '1'){
for(var j = 0; j < RP.dosage.No301.quantity; j++) {
var title = take_timing + "の食" + before_after + "の薬(未)";
var start_time = getStartTime(take_timing, before_after, j);
var end_time = getEndTime(start_time);
var description = getDescription(RP.drugs);
createEvent(title,description,'0',start_time,end_time);
}
}
}
}
リスト7.剤(PR)単位でカレンダーに服薬予定を書き込むyouho_hyouzi関数
Googleカレンダーに服薬予定を書き込むために開始日時、終了日時、説明(description)等が必要になってくる。以下はそのための関数である。
function getStartTime(take_timing, before_after, offset){
var start_time = new Date();
start_time.setDate(start_time.getDate() + offset);
// 設定取得
var settings = getSettings();
//参考演算子(前の時は0,後の時は30が入る)
var minutes = before_after == "前" ? 0 : 30;
if(take_timing == "朝"){
start_time.setHours(settings.morning.hours, settings.morning.minutes + minutes, 0, 0);
} else if (take_timing == "昼"){
start_time.setHours(settings.lunch.hours, settings.lunch.minutes + minutes, 0, 0);
} else if (take_timing == "夜"){
start_time.setHours(settings.dinner.hours, settings.dinner.minutes + minutes, 0, 0);
} else if (take_timing == "寝"){
start_time.setHours(settings.sleep.hours, settings.sleep.minutes, 0, 0);
}
return start_time;
}
リスト8.timingと食前・後を引数にして開始日時を返すgetStartTime関数
ここで、getSettings()関数は、Google spreadsheet (ファイル名:pill-reminder)から設定情報を読み取って返す関数で、 settings.morning、settings.lunch、settings.dinner、settings.sleepは、それぞれ朝食、昼食、夕食、そして就寝する時刻で、各々hours(時)とminutes(分)のプロパティを持つ。
function getEndTime(start_time){
var end_time = new Date(start_time.getYear(), start_time.getMonth(), start_time.getDate(),start_time.getHours()+1,start_time.getMinutes(),start_time.getSeconds() );
return end_time;
}
リスト9.開始日時(start_time)から1時間後を設定するgetEndTime関数
function getDescription(drugs){
var description = '';
for(var i = 0; i < drugs.length; i++){
description += (description == '' ? '' : '\n') + drugs[i].No201.name + ' ' + drugs[i].No201.dose + drugs[i].No201.unit;
}
return description;
}
リスト10.descriptionに入れる薬品名を編集するgetDescription関数
Googleカレンダーにイベント(予定の単位)を書き込むのが次のcreateEvent関数である。イベントの色は赤色に設定する。
function createEvent(title,description,location,start_time,end_time){
//start_time~end_timeまでの時間が1つのイベントの時間
//イベントを作成する(p.314参考に)
var event = CalendarApp.getDefaultCalendar().createEvent(
//タイトルを書き込む
title,
//時間設定
start_time,
end_time,
{
//説明(description)に書き込む
description:description,
location:location
}
);
//作成後に色は変更しないといけないらしい。
event.setColor(CalendarApp.EventColor.RED);
}
リスト11.Googleカレンダーにイベントを書き込むcreateEvent関数
用法によって朝・昼・夜・就寝に薬を飲むかどうか、またそれは食前か食後かがわかる。 入力データ中にある用法からこれらのパラメタを簡単に取り出せるように連想配列として用法をテーブルにしたものが下記のdosage_table である。
var dosage_table = {
"【1日4回毎食後及び就寝前に】": {
"朝": "1",
"昼": "1",
"夜": "1",
"食": "後",
"寝": "1"
},
"【1日3回毎食前に】": {
"朝": "1",
"昼": "1",
"夜": "1",
"食": "前",
"寝": "0"
},
"【1日3回毎食後に】": {
"朝": "1",
"昼": "1",
"夜": "1",
"食": "後",
"寝": "0"
},
"毎食後服用": {
"朝": "1",
"昼": "1",
"夜": "1",
"食": "後",
"寝": "0"
},
"【1日2回朝夕食前に】": {
"朝": "1",
"昼": "0",
"夜": "1",
"食": "前",
"寝": "0"
},
"【1日2回朝食後及び就寝前に】": {
"朝": "1",
"昼": "0",
"夜": "0",
"食": "後",
"寝": "1"
},
"【1日2回朝夕食後に】": {
"朝": "1",
"昼": "0",
"夜": "1",
"食": "後",
"寝": "0"
},
"【1日1回朝食後に】": {
"朝": "1",
"昼": "0",
"夜": "0",
"食": "後",
"寝": "0"
},
"【1日1回昼食後に】": {
"朝": "0",
"昼": "1",
"夜": "0",
"食": "後",
"寝": "0"
},
"【1日1回夕食後に】": {
"朝": "0",
"昼": "0",
"夜": "1",
"食": "後",
"寝": "0"
},
"【1日1回朝食前に】": {
"朝": "1",
"昼": "0",
"夜": "0",
"食": "前",
"寝": "0"
},
"【1日1回昼食前に】": {
"朝": "0",
"昼": "1",
"夜": "0",
"食": "前",
"寝": "0"
},
"【1日1回夕食前に】": {
"朝": "0",
"昼": "0",
"夜": "1",
"食": "前",
"寝": "0"
},
"【1日1回就寝前に】": {
"朝": "0",
"昼": "0",
"夜": "0",
"食": "前",
"寝": "1"
},
}
リスト12.用法テーブル
/**
* デバッグ出力
* 出力先:Googleドライブ内のdebugという名前のGoogleドキュメント
*
*/
function debug(meseage){
var doc = DocumentApp.openById('1cWo6WrYhQvqEQC80TEtisdsKNcYiog9uDffx8GIDM9E');
var body= doc.getBody();
body.appendParagraph(formatDate(new Date()) + '(A1):' + meseage);
}
/**
* 日付をフォーマットする
* @param {Date} date 日付
* @param {String} [format] フォーマット
* @return {String} フォーマット済み日付
*/
function formatDate(date, format) {
if (!format) format = 'YYYY-MM-DD hh:mm:ss.SSS';
format = format.replace(/YYYY/g, date.getFullYear());
format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
format = format.replace(/hh/g, ('0' + date.getHours()).slice(-2));
format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
if (format.match(/S/g)) {
var milliSeconds = ('00' + date.getMilliseconds()).slice(-3);
var length = format.match(/S/g).length;
for (var i = 0; i < length; i++) format = format.replace(/S/, milliSeconds.substring(i, i + 1));
}
return format;
};
リスト13.debug関数と日時フォーマット関数
WebAPI実行時はLogger.log関数が使えないので、その代わりにGoogleドキュメントにログを記録するのがこのdebug関数である。リスト13においてDocumentApp.openByIdの引数'1cW.......................M9E'はデバッグ情報を書き込むためい作成したGoogleドキュメントのIDである。
設定情報の取得API
Google spreadsheetに格納してある設定情報(朝・昼・夕食の時刻および就寝時刻)を取得して返却するWebAPIはGETメソッドとして実装している(リスト14)。
function doGet(e) {
// 設定情報を取得する
var response = getSettings();
// Textoutputオブジェクトを生成する
var out = ContentService.createTextOutput();
// Mime TypeをJSONに設定
out.setMimeType(ContentService.MimeType.JSON);
// JSONテキストをセットする
out.setContent(JSON.stringify(response));
return out;
}
リスト14.設定情報の取得WebAPI
リスト4で呼び出される設定情報の書き込み関数setSettings()および リスト14で呼び出される設定情報の読み込み関数getSettings()を下記に示す。
/**
* 設定書き込み
* 設定ファイル:pill-reminder
* id:1TRTAePhv86IHOtpC3acLlnGrSmw8M21rT0GWdv-TbbY
* @param {Object} settings 設定情報
*/
function setSettings(settings) {
Logger.log(JSON.stringify(settings, null, ' '));
var spreadsheet = SpreadsheetApp.openById('1TR....................bbY');
var sheet = spreadsheet.getSheetByName('設定');
sheet.getRange("B2").setValue(settings.morning.hours);
sheet.getRange("C2").setValue(settings.morning.minutes);
sheet.getRange("B3").setValue(settings.lunch.hours);
sheet.getRange("C3").setValue(settings.lunch.minutes);
sheet.getRange("B4").setValue(settings.dinner.hours);
sheet.getRange("C4").setValue(settings.dinner.minutes);
sheet.getRange("B5").setValue(settings.sleep.hours);
sheet.getRange("C5").setValue(settings.sleep.minutes);
}
/**
* 設定ローディング
* 設定ファイル:pill-reminder
* id:1TRTAePhv86IHOtpC3acLlnGrSmw8M21rT0GWdv-TbbY
* @return {Object}
*/
function getSettings() {
var spreadsheet = SpreadsheetApp.openById('1TR....................bbY');
var sheet = spreadsheet.getSheetByName('設定');
var settings = {
morning: {hours: sheet.getRange("B2").getValue(), minutes: sheet.getRange("C2").getValue()},
lunch: {hours: sheet.getRange("B3").getValue(), minutes: sheet.getRange("C3").getValue()},
dinner: {hours: sheet.getRange("B4").getValue(), minutes: sheet.getRange("C4").getValue()},
sleep: {hours: sheet.getRange("B5").getValue(), minutes: sheet.getRange("C5").getValue()},
};
Logger.log(JSON.stringify(settings, null, ' '));
return settings;
}
リスト15.設定情報の設定と取得関数
なお、設定情報はidが'1TR....................bbY'のGoogle spreadsheetに次のように格納されている。
| 図1.設定情報を格納するGoogle Spreadsheet |
設定クライアント
朝・昼・夕食時刻、就寝時刻はスマホ側から当該WebAPIを利用して設定を行う。下記はそのサンプルHTMLである。ただし、これはMonacaによるスマホアプリではなく、ブラウザアプリである。
<!--
A1-settings-test
Google spreadsheet(ファイル名:pill-reminder)に格納した設定情報の取得
semi2018kumw
-->
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Google spreadsheetに格納した設定情報の取得</title>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script>
// ページが完全に読み込まれたら実行される
$(document).ready(function() {
putLog('設定情報読み込み中・・・');
$.ajax({
url: 'https://script.google.com/macros/s/AKfycbypuEBKz9Z00NaRifXx-weMVGuSyXZjt_pWCyvdRRhHOl8NjarP/exec',
type: 'GET',
dataType: 'json',
})
.done( (data, textStatus, errorThrown) => {
putLog('設定情報の取得に成功 textStatus:' + textStatus);
putLog(JSON.stringify(data, null, ' '));
$('#morning-hours').val(data.morning.hours);
$('#morning-minutes').val(data.morning.minutes);
$('#lunch-hours').val(data.lunch.hours);
$('#lunch-minutes').val(data.lunch.minutes);
$('#dinner-hours').val(data.dinner.hours);
$('#dinner-minutes').val(data.dinner.minutes);
$('#sleep-hours').val(data.sleep.hours);
$('#sleep-minutes').val(data.sleep.minutes);
})
// Ajaxリクエストが失敗した時発動
.fail( (data, textStatus, errorThrown) => {
putLog('設定情報の取得に失敗 textStatus:' + textStatus);
putLog(JSON.stringify(data, null, ' '));
})
});
// 送信ボタンが押されたときの処理(設定情報の送信)
$(document).on('click','#send',function(){
var settings = {
"settings": {
"morning": {
"hours": $('#morning-hours').val(),
"minutes": $('#morning-minutes').val()
},
"lunch": {
"hours": $('#lunch-hours').val(),
"minutes": $('#lunch-minutes').val()
},
"dinner": {
"hours": $('#dinner-hours').val(),
"minutes": $('#dinner-minutes').val()
},
"sleep": {
"hours": $('#sleep-hours').val(),
"minutes": $('#sleep-minutes').val()
},
}
}
putLog(JSON.stringify(settings, null, ' ') + '<br><br>送信中・・・');
$.ajax({
url: 'https://script.google.com/macros/s/AKfycbypuEBKz9Z00NaRifXx-weMVGuSyXZjt_pWCyvdRRhHOl8NjarP/exec',
type: 'POST',
data: JSON.stringify(settings),
dataType: 'json',
})
.done( (data, textStatus, errorThrown) => {
alert('設定情報の送信に成功 textStatus:' + textStatus);
putLog(JSON.stringify(data, null, ' '));
})
// Ajaxリクエストが失敗した時発動
.fail( (data, textStatus, errorThrown) => {
alert('設定情報の送信に失敗 textStatus:' + textStatus);
putLog(JSON.stringify(data, null, ' '));
})
});
// debugメッセージ出力
function putLog(msg) {
$('#log').html('<pre>' + msg + '</pre><br />');
}
</script>
</head>
<body>
<h1>A1-settings-test</h1>
<table>
<tr>
<th align="left">服薬時刻</th>
<th align="center">時</th>
<th align="center">分</th>
</tr>
<tr>
<th align="left">朝食</th>
<td>
<select id="morning-hours">
<script>
for (var hours = 0; hours < 24; hours++) {
document.write('<option value="' + hours + '">' + hours + '</option>');
}
</script>
</select>
</td>
<td>
<select id="morning-minutes">
<script>
for (var minutes = 0; minutes < 60; minutes++) {
document.write('<option value="' + minutes + '">' + minutes + '</option>');
}
</script>
</select>
</td>
</tr>
<tr>
<th align="left">昼食</th>
<td>
<select id="lunch-hours">
<script>
for (var hours = 0; hours < 24; hours++) {
document.write('<option value="' + hours + '">' + hours + '</option>');
}
</script>
</select>
</td>
<td>
<select id="lunch-minutes">
<script>
for (var minutes = 0; minutes < 60; minutes++) {
document.write('<option value="' + minutes + '">' + minutes + '</option>');
}
</script>
</select>
</td>
</tr>
<tr>
<th align="left">夕食</th>
<td>
<select id="dinner-hours">
<script>
for (var hours = 0; hours < 24; hours++) {
document.write('<option value="' + hours + '">' + hours + '</option>');
}
</script>
</select>
</td>
<td>
<select id="dinner-minutes">
<script>
for (var minutes = 0; minutes < 60; minutes++) {
document.write('<option value="' + minutes + '">' + minutes + '</option>');
}
</script>
</select>
</td>
</tr>
<tr>
<th align="left">就寝</th>
<td>
<select id="sleep-hours">
<script>
for (var hours = 0; hours < 24; hours++) {
document.write('<option value="' + hours + '">' + hours + '</option>');
}
</script>
</select>
</td>
<td>
<select id="sleep-minutes">
<script>
for (var minutes = 0; minutes < 60; minutes++) {
document.write('<option value="' + minutes + '">' + minutes + '</option>');
}
</script>
</select>
</td>
</tr>
</table>
<p>
<button id="send">送信</button>
</p>
<div id="log"></div>
</body>
</html>
リスト16.設定クライアント(ブラウザアプリ)
0 件のコメント:
コメントを投稿