【GAS】バ…ドリ公式サイトの今日の予定を教えてくれるLINE botを作った

 先日、バンドリ公式HPのスケジュールをGoogleカレンダーに登録するスクリプトを作った。

gassperi.hatenablog.com

 でも正直毎朝Googleカレンダーをチェックしないな……と作ってから気づいた(しかも予定が多すぎて見辛かった)ので、今日の予定をプッシュ通知してくれたら嬉しいな~と思った。以前DiscordやSlackにプッシュ通知する仕組みをGoogle Apps Script(GAS)で作ったので、それでやろうかなとも思ったけど、DiscordもSlackも最近はあんまり使わなくなってしまったので、であればLINEのbotでやってみようと思った。ググったら当然のように先駆者がいたので参考にさせていただいた。ありがとうございます。
LINE botを用意するまで↓ qiita.com

プッシュ通知を実現する↓ qiita.com

スクレイピングするのは前回のソースを流用して、LINE botに通知するのは参考リンクのコードをほぼ丸パクリした。ありがとうございます。

 あと、今更知ったけどGASのトリガーって時間指定で実行できないらしく、そうしたい場合はスクリプトの中でトリガーを作るってことをしないといけない。下記リンクを参考にした。

tonari-it.com

作りっぱなしだと汚くなるので作ったトリガーを消す処理も同サイトから。

tonari-it.com

作ったものはBitbucketにあげた。結果的にほとんど丸パクリでした。 https://bitbucket.org/zaki3mymy/linebotscheduler
botは↓のQRコードで友達に登録できる。毎朝7時に今日の予定を教えてくれるはず。
※2020/08/06追記
友達数やメッセージを送れる数に限度があるようなので公開やめました。

※2020/07/30追記
グループに招待してもメッセージを送ってくれないことがわかったので後日調査する。
※2020/08/06追記
グループに送るにはgroupIdが必要で、グループ追加時にそれを得られるが保管場所とかを考えると面倒くさくなったのでやめた。個人用だし、そこまでの機能は要らないかな……。

【GAS】バン…リ公式サイトのスケジュールをGoogleカレンダーに登録するスクリプト作った

 バンドリ公式サイトのスケジュール、Googleカレンダーにしてくれんかなぁと思ってたけど一向にそうなる気配がないので、HTMLをパースしてGoogleカレンダーに登録するスクリプトを作った。ソースは↓に置いた。 https://bitbucket.org/zaki3mymy/gbpscheduler/src/master/
 雰囲気↓みたいな感じになった。

f:id:gassperi:20200705194011p:plain
予定が多すぎる

以下のリンクからGoogleカレンダーに追加可能だが、共有したカレンダーには色分けは反映されないらしい(そんな……)。
https://calendar.google.com/calendar?cid=NzAzZXZjaGgxbnFxdGg0NnVkdnFkaDM3NHNAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ

iCal形式は↓
https://calendar.google.com/calendar/ical/703evchh1nqqth46udvqdh374s%40group.calendar.google.com/public/basic.ics

とりあえず2020年内の予定(2020/07/05時点)は追加した。

実装まで

 バンドリ公式サイトのスケジュールは↓ bang-dream.com  クエリパラメータymで年月を指定すれば、その年月のスケジュールが表示されるっぽい。例えば?ym=202007みたいにすれば2020年7月のスケジュールが出る。ぬっ……っと表示されるのでjsで表示してるのかと思ったけど、各予定はHTML要素として予め存在してたので問題なさそうだった。
 実装はGoogle Apps Script(GAS)上で行った。月イチとかで定期実行できるようにすると良いかなと思ったし、以前GmailのパースをGASでやったのもあってそうした。パースはparserってライブラリ(https://github.com/tadaken3/html-parser-gas)を使う。
 GASってgitで管理とかできないのかなぁと思って調べてみたらclaspというCLIツールを見つけた。ローカルのソースをGASにpushしたりpullしたり……という様なツールらしい。以下の記事を参考にした。

qiita.com

といっても動作確認はGAS上でしかできないので、結局GASでスクリプト書いてローカルにclasp pullしてgit commitして……みたいな進め方になった。あと、予定重複して登録しちゃうのでそこの制御がめんどくて、まっさらなカレンダーに対してのみ登録する自分本意なスクリプトになった。月イチで定期実行とは……(こういうのやめな)。

【Java8】 .propertiesファイルからキャストしつつ値を取得する

環境

$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b03-0ubuntu1.18.04.1-b03)
OpenJDK 64-Bit Server VM (build 25.212-b03, mixed mode)

結論

以下のPropertiesWrapperがそれ用のクラス。

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.Double;
import java.lang.Integer;
import java.util.Properties;
import java.util.function.Function;

public class PropertiesTest {
    public static void main(String[] args) {
        PropertiesWrapper props = new PropertiesWrapper("./test.properties");
        
        String foo = props.getValue("foo", "defaultValue", (s) -> s);
        int bar = props.<Integer>getValue("bar", 0, (s) -> Integer.parseInt(s));
        double baz = props.<Double>getValue("baz", 0.1, (s) -> Double.parseDouble(s));
        
        System.out.println(foo);
        System.out.println(bar);
        System.out.println(baz);
    }
}

class PropertiesWrapper {
    private Properties props;
    
    public PropertiesWrapper(String filepath) {
        this.props = new Properties();
        
        try (InputStream inputStream = new FileInputStream(filepath)){
            props.load(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public <T> T getValue(String key, T defaultValue, Function<String, T> castFunc) {
        String value = props.getProperty(key);
        T cast;
        try{
            cast = castFunc.apply(value);
        }catch(Exception e){
            cast = defaultValue;
        }
        return cast;
    }
}

過程

↓みたいに色んな型の値がある.propertiesファイルがあるとする。

foo=x
bar=100
baz=5.0

Javaで.propertiesファイルから読んだ値はString型で取得されるため、数値を読み込むにはキャストする必要がある。

Properties props = new Properties();
String value = props.getProperty("key");

int x = Integer.parseInt(value);
double y = Double.parseDouble(value);
float z = Float.parseFloat(value);

取得した値がキャストできないとNumberFormatExceptionが発生するので変換処理を集約したい。そこで以下のようなjava.util.Propertiesクラスのラッパークラスを作る。getValue()で.propertiesファイルから値を取得する。

class PropertiesWrapper {
    private Properties props;
    
    public PropertiesWrapper(String filepath) {
        this.props = new Properties();
        
        try (InputStream inputStream = new FileInputStream(filepath)){
            props.load(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public Object getValue(String key) {
        // このメソッドにキャスト処理を集約したい
        return props.getProperty(key);
    }
}

getValue()の中でキャストしたりエラーハンドリングしたりしたい。先に書いたように、数値型へのキャストはprimitive型のラッパークラスを用いる。なのでジェネリクスを使ってみる。長いのでメソッド部分だけ。

public <T> T getValue(String key) {
    String value = props.getProperty(key);
    return (T) value;
}

.propertiesファイルから取得した値を型パラメータTでキャストして返している(コンパイル時に未検査のキャストの警告が出る)。呼ぶときは例えばpropertiesWrapper.<Integer>getValue("bar")などとして呼ぶ。実行すると

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at PropertiesTest.main(PropertiesTest.java:12)

みたいなエラーが出る。java.lang.Stringクラスとjava.lang.Numberクラスには何の関連もないので(T)みたいなキャストは無効。なので、キャストする方法も一緒に渡してみる。

public <T> T getValue(String key, Function<String, T> castFunc) {
    String value = props.getProperty(key);
    T cast = castFunc.apply(value);
    return cast;
}

String型を受け取りT型の値を返すFunction型の関数型インターフェースcastFuncを受け取るようにした。実際に動かしてみる。

public class PropertiesTest {
    public static void main(String[] args) {
        PropertiesWrapper props = new PropertiesWrapper("./test.properties");
        
        int bar = props.<Integer>getValue("bar", (s) -> Integer.parseInt(s));
        System.out.println(bar); // -> 100
    }
}

test.propertiesにあるbarの値をInteger.parseIntでキャストする。ラムダ式の部分はメソッド参照(Integer::parseInt)でもいいけど、メソッド参照はラムダ式よりも遅いってどっかで見た。やったら実際遅かった。ちなみにちゃんとしたT型を返すようになったので、さっき出ていた未検査のキャストの警告は消えている。

キャストにミスったときはデフォルトの値を返すことにして、エラーハンドリングも加味した結果、以下のようになった。

public <T> T getValue(String key, T defaultValue, Function<String, T> castFunc) {
    String value = props.getProperty(key);
    T cast;
    try{
        cast = castFunc.apply(value);
    }catch(Exception e){
        cast = defaultValue;
    }
    return cast;
}

感想

結局.propertiesファイルから取得するときにキャスト用の関数をその都度渡しているのがなんか…。でもキャストと言いつつ、castFuncの実装によっては適当なエラーチェックなどを挟めるのはいいかもしれない。まあこのタイミングでやることか?という疑問はある。

添寝

『そいねドリーマー』を読んだ。長編というか本自体読むの久しぶりだったけど、文章が読みやすかったのですいすい読み進められた。ジャンルとしてはSF?百合要素はある。

途中で夢中夢みたいな構造に気づいたので、最初の方(保健室で沙耶とひつじが同衾したときの夢)からずっと夢の中の話で、スリープウォーカーや睡獣なんてのは本当にただの夢に出てきた概念でしかなくて、最後に目を覚ましたベッドは保健室で、、、というのが良いなぁと思った。不眠症の女の子が、全く知らない人なのにある女の子に添い寝してもらったら眠ることができた。その夢の中でその子と一緒に色々な経験をして、目が覚めたときにはその女の子と親しくなっていた、みたいな。

最近聴いて良かった曲

AnimalJam - AnimalJam

スクリーモ/ポストハードコア。DGDやHail the Sunをジャンルとしてのエモに寄せたような印象。1曲目のイントロでもうエモい。曲のうまいところにクリーンパートを入れてきてて良い。5曲で軽く聴けるし、この出来でデビューEPらしいので今後期待。

Virtual Self - Angel Voices

トランス(?EDMとかそっちのジャンル分けわからん)。クッソ重いパートから細かく刻むシンセが入ってくるの気持ちよい。オタク好みだと思う。Porter Robinsonのプロジェクトらしい。Porter RobinsonってShelterしか知らなかったけど、調べたら日本文化かなり好きらしい。まあ好きじゃなきゃ自分でアニメMVの監督やって日本のアニメーション会社にMV作らせないか。ロゴもエヴァの感じるし、後はserial experiments lainかなぁ。EPはSoundCloudでも聴けるぞ。

アイカツスターズ! - 未来トランジット

1月から見始めた『アイカツ!』を半年で見終わり、最近『アイカツスターズ!』を見始めたわけだけど、8話でようやく見るの慣れてきた気がする。というか多分8話で香澄真昼ちゃんが出てきたのも大きい気がする。かわいい。で、8話でお姉ちゃん(香澄夜空)がライブでやった曲がこれ。7話までだとライブ曲がそこまでピンとこなかったけど、やっぱりセクシー路線の曲は好みだった。あとEDの『episode Solo』が激強すぎる。Perfume的なダブステップと言えばいいのかな。これ最初のEDなのかなり期待値上がってしまう。

やっと2018年の速度に慣れてきたなと思ったらもう7月も終わりそうだし、ほんとにあっという間に2018年が終わってしまうな。

2018年夏

2018年夏

 少女☆歌劇 レヴュースタァライトが幾原邦彦監督っぽい演出でブワァァと持っていかれたので想定外に良かった。完全にダークホースだった。情報量が多すぎて1回見ただけでは何が何だかわからんやつ好きすぎる。CM見たときは「ふぅん、ミュージカルねぇ…」みたいなこと思ってたので、やっぱり見てから決めないとなぁと思った。白背景に縦長フォントほんと好き。音楽も藤澤さんだしめちゃ楽しみ。  はねバド!は試合シーンがめちゃくちゃ動いて良かった。2話でなぎさのスランプ復帰までやったけど、笑顔でスマッシュ打つなぎさは見たかったなぁと思った。あとOP:YURiKA / ED:大原ゆい子って完全に宝石の国なんだよな。

 春は結局あんまり見れなかったので夏はちゃんと見れるかも。平日は家にいないのハンディキャップすぎる。

ダリフラ感想

 ダリフラ、VIRM出てきたあたりで話がややこしい感じになってきて科学的な伏線も回収されてないものがあるのかもしれないけど、イクノだけは個人的に良かった。男と女ならこの世界に何か(子供を)残せるんだという556の主張に対して、では男性性を受け入れることができない自分は何も残せないのかという問題を抱えていたので、自分の生命を分かつ子を設けることよりも他者の子を、ひいては社会全体を存続させる(残していく)ことができるようになれたのはベターな解決策だったなと思う(ベストはない)。その過程でナオミという理解者を得ることができたのも良かったよ。ヒロがナオミとうまくいかなかったのは、ナオミもイクノと同じような悩みを抱えていたかなぁと今になって思う。イクノがナオミとどこまで深い関係性になったのかは明示されていないけど、研究の合間にかつてのイチゴへの思いをナオミへ吐露するイクノはいたし、そのことを受け入れるナオミもいたんだよな。