2020年5月30日土曜日

あつ森で集めたカブ価をグラフに出してくれるしずえさんbot

しずえさんbotは、遊び友達のグループLINEでつかっているLINE botです。
元々、はれんちさんが作ったbotに下の画像のようにグラフ出力機能を追加しました。

グラフを返してくれるしずえさん

しずえさんbotの仕組み


GASを利用したLINE bot

  1. LINEメンバーにカブ価を午前・午後で入力してもらう。
  2. 「あきおの今週のカブ価」や「今日のカブ価」をLINEに投げる。
  3. LINE botがメッセージをGASに送る。
  4. GASで処理できる定型文であればスクリプトを起動する。
  5. スクリプトの結果がLINEに返ってくる。
どうやってGASでLINE botを実装するのかについては詳しく知らないので、はれんちさんのブログを待ちたいと思います(期待)。

今日のカブ価がわかる


実装したグラフ出力機能


集計しているデータ形式は次のような形です。



コードはこちらです。実行する場合はGASにMomentライブラリを追加してください。
// シートに登録されている内容から今週のカブ価チャートを作成する
// crateChart('あきお') -> 'あきお'のカブ価チャートのURLを返す
function createChart(name='あきお') {
// データが集められているスプレッドシートを取得する
const id = "<your_sheet_id>";
const spreadSheet = SpreadsheetApp.openById(id);
const sheetName = "フォームの回答 1";
const sheet = spreadSheet.getSheetByName(sheetName);
// データの行数を取得する
const existValueLastRow = getExistValueLastRow(sheet);
// 全登録データを取得する
const range = sheet.getRange(`A2:D${existValueLastRow}`).getValues();
// 選択された人のデータを抽出する
const filtered = range.filter(r => r.includes(name));
// 抽出されたデータが0件ならURLを返さず、この関数の処理を終了する。
if (filtered.length === 0) return;
// 抽出されたデータから、よりデータを扱いやすいRecordドメインの配列に詰め替える
const records = recordsFactory(filtered);
// 今日の曜日を取得する。今日が月曜日ならnowMomentは1、今日が土曜日ならnowMomentには6が入る
const nowMoment = Moment.moment().day();
// 直近の日曜日の日付を作る
const lastSunday = Moment.moment().add('days', -nowMoment).set('hour', 0).set('minute', 0);
// 今週のカブ価を抽出する
const thisWeekRecord = records.filter(record => record.isAfter(lastSunday));
// ここまででデータの抽出は終わり。ここからはチャート出力の処理
// チャートに表示しやすいように今週のデータをつくる。詳しくはこの関数を見てください。
let weeklyRecord = weeklyRecordFormat(thisWeekRecord, lastSunday, name);
// チャートデータを作り、何が入っているのかを教える。
let chartData = Charts.newDataTable()
.addColumn(Charts.ColumnType.STRING, '日付')
.addColumn(Charts.ColumnType.NUMBER, 'ベル');
// チャートデータをプロットする
weeklyRecord.map(record => chartData.addRow(record.getChartData()));
// チャートの見た目を整え、画像データにする
let chart = Charts.newLineChart()
.setDataTable(chartData)
.setTitle(`${name}のカブ価`)
.setColors(["#EF5F32"])
.setPointStyle(Charts.PointStyle.MEDIUM)
.setOption('legend.position', 'in')
.setOption('chartArea', {left: 50, right: 50})
.setDimensions(650, 300)
.build().getBlob();
// 画像データを所定の位置に保存する
var folderId = '<your_folder_id>'; // Googleドライブの一時フォルダのID
var today = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'YYYY-MM-dd');
var folder = DriveApp.getFolderById(folderId);
var file = folder.createFile(chart)
file.setName(today);
// 保存した画像を公開設定する
file.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT)
// 画像URLを返す
return file.getDownloadUrl();
}
// 登録データの最後の行を取得する関数
function getExistValueLastRow(sheet) {
const columnACellValues = sheet.getRange('A:A').getValues();
return columnACellValues.filter(String).length;
}
// 登録データからRecordオブジェクトの配列を返す関数
function recordsFactory(rawRecords) {
let records = [];
rawRecords.map(rawRecord => {
records = records.concat([recordFactory(rawRecord)]);
});
return records;
}
// 登録データをRecordオブジェクトに詰め直す関数
function recordFactory(rawRecord) {
//[Wed Apr 01 2020 01:40:25 GMT+0900 (日本標準時),Sun Mar 22 2020 11:00:00 GMT+0900 (日本標準時),あきお,105]
let price = rawRecord[3];
if (rawRecord[3] === null || typeof rawRecord[3] === 'undefined' || rawRecord[3] === '') {
price = null;
}
// 登録日と実日があるので、実日があるか見て場合わけする
if (Moment.moment(rawRecord[1]).isValid()) {
return new Record(Moment.moment(rawRecord[1]), rawRecord[2], price);
}
return new Record(Moment.moment(rawRecord[0]), rawRecord[2], price);
}
// Recordオブジェクト
class Record {
constructor(date, name, price) {
this.date = date;
this.name = name;
this.price = price;
}
getName() { return this.name; }
getPrice() { return this.price; }
getDate() { return this.date.isValid() ? this.date.format('M/DD A') : null; }
getRawDate() { return this.date; }
getChartData() { return [this.getDate(), this.price]; }
isAM() { return this.date.isValid() ? this.getDate().includes('AM') : false; }
isSunday() {
return this.date.isValid() ? this.date.format('dddd曜日').includes('日曜日') : false;
}
isAfter(date) {
return this.date.isValid() ? this.date.isAfter(date) : false;
}
}
// チャートを作るために1週間分のデータ形式を作っている関数
// もし、登録データがない日があった場合、チャートの日付がずれるのでこの関数で補正してる
function weeklyRecordFormat(thisWeekRecord, lastSunday, name) {
let dateAM = Moment.moment(lastSunday).set('hour', 0);
let datePM = Moment.moment(lastSunday).set('hour', 12);
let records = [];
// 1週間の記録は最大14回分ある
for (let step = 0; step < 14; step++) {
let isEvenStep = step % 2 === 0;
let featureDate = isEvenStep ? dateAM : datePM;
records = records.concat(fillRecord(featureDate, thisWeekRecord, name));
isEvenStep ? dateAM.add('days', 1) : datePM.add('days', 1);
}
return records;
}
// 登録データがあるか見て、あれば使い、なければカブ価をnull(価なし、0ベルとは異なる)にしている
function fillRecord(date, thisWeekRecord, name) {
let fdate = date.format('M/DD A');
let matchDate = thisWeekRecord.filter(record => record.getDate() === fdate);
// 日曜日は例外としてAMもPMも関係なく出力するようにする
let isSunday = date.day() === 0;
if (isSunday) { matchDate = thisWeekRecord.filter(record => record.getDate().includes(date.format('M/DD'))); }
if (matchDate.length === 0) return [new Record(Moment.moment(date), name, null)];
let matching = matchDate.slice(-1)[0]; // 打ち間違いを考慮して、最後のレコードを採用する
return [new Record(Moment.moment(matching.getRawDate()), matching.getName(), matching.getPrice())];
}
// テスト関数(本来の処理では使っていない)
// ここではweeklyRecordFormat()の実装が不安だったので、挙動が本当に意図通りか見るために書いている
function testWeeklyRecordFormat() {
let lastSunday = Moment.moment().add('days', -(Moment.moment().day())).set('hour', 0).set('minute', 0);
let test =
weeklyRecordFormat(
[new Record(Moment.moment(), 'あきお', 105),
new Record(Moment.moment().add('days', -1), 'あきお', 100),
new Record(Moment.moment().add('days', -2), 'あきお', 95),
],
lastSunday,
'あきお'
);
test.map(r => console.log(r.getChartData()));
}
view raw createChart.gs hosted with ❤ by GitHub

わからないところがあれば、コメントで質問していただけたらと思います。

ただちょっと懸念があります。
  • 出力速度が遅い(大体4秒くらい)
  • 所定のフォルダにグラフが溜まっていく

SheetのAPIを叩く回数が減れば出力速度も小さくなると思います。
上のコードを使った際は改善してみてください。

参考

2020年5月27日水曜日

週末にチョットツヨイPC自作した

ゲームと配信がしたくてPCを自作しました。
Twitterの低レイヤ界隈の人たちみたいに「CPUから作る」のではなく、普通にパーツを買ってます。

記念撮影

パーツは
  • CPU
    • AMD Ryzen 7 3700X
  • GPU
    • MSI GeForce RTX 2060 AERO ITX
  • メモリ
    • Team DDR4 3200Mhz(PC4-25600) 16GBx2枚(32GBkit)
  • M2ストレージ
    • WD SSD 500GB M.2-2280 NVMe
  • HDD
    • WD HDD 1TB WD Black
  • その他
    • MSI B450 GAMING PLUS MAX ATX マザーボード
    • Thermaltake Versa H26 Black ミドルタワー型PCケース
    • I-O DATA WiFi 無線LAN 子機 11ac 1300Mbps
    • Windows 10 Home 日本語版/May 2019 Update適用/パッケージ版

こだわりはRTX20シリーズとRyzenを使うことでした。
友達からM2は載せた方がいいと強く推されたので使ってます。(めちゃくちゃよかった!)
他は基本的にARTJUKUさんの予算別自作PC構成を参考にしています。
全部で15万円くらいです。他のBTOと比べて2~3万は安かったです。
給付金もあるし。

マザボの組み立ては絶縁プラを下敷きにするらしい
CPUクーラーの向きは一度ミスりました。要注意です。
光ってる!!!!
組み立てた感想

「OSの立ち上がりが早い!!!(M2のおかげ)」
「光ってる!!(重要)」
「ケースにもっと気を貼ってもいいな!(電源からの配線が難しかった)」
「BTOに2~3万追加してもいいな!(組み立てに半日かかった)」

ただ、普段使っている機器がどういう繋がりで動いているのかがわかるので、一度は経験しておいてもいいですね。
あと、組み立てる前にめちゃくちゃパーツの情報を調べるので、そこが勉強になりました。

参考まで。