Google Home でツイッターをする

新しい職場で開かれたとあるコンテストで入賞し、GoogleHomeを頂いたので遊んでみました。

今回作った機能は下記の3つです

  1. 「OK,Google ツイート {ツイート内容}」と言うと{ツイート内容}がツイートされる。
  2. 「OK,Google リプライ {ツイート内容}」と言うと{ツイート内容}が一番最後に自分に対してリプライをしたユーザのツイートに対してリプライされる。
  3. 自分へのリプライが行われると、GoogleHomeがリプライ内容を喋る。

1.ツイートする

qiita.com

上の記事を参考に、基本的にはIFTTTで実装しています。

「おっけーぐーぐる ツイート 外に出る気力がない」

と言うと以下のようにツイートされるようになりました。

f:id:anoChick:20171015221530p:plain

ただ、これだとツイート内容の単語間にスペースがはいってしまっています。

そのため、Firebase Functionsを経由し、そこで整形を行った後にツイートするというようにしました。

exports.tweet = functions.https.onRequest((request, response) => {
  var postParams = {
    status: request.body.text.replace(/ /g, '') + " - Google Home から"
  }
  client.post('statuses/update', postParams);
  response.status(200).end();
});

f:id:anoChick:20171015221901p:plain

出来ました。

2.リプライする

リプライについても同様に、IFTTTからFIrebase FunctionsのエンドポイントURLを叩き、内部でツイッターAPIを使って最後のリプライを指定、ツイート。と言うような感じです。

f:id:anoChick:20171015224416p:plain

つまづきポイントとしては、IFTTTの音声認識の設定を「リプライ $」とすると認識されづらく、「reply $」でも認識されるようにしたぐらいです。

3.自分へのリプライを喋る

qiita.com

↑を参考にしました。 GoogleHomeを喋らせるようのサーバを建て、発話APIを作ります、 IFTTTをつかってリプライが来たらwebhookによって発話APIを叩くようにします。

こんな感じで、リプライが来ると喋りだすようになりました。 上のデモでは、「@anoChick おk」というツイート内容を発話しており、「@anoChick」の部分を抜きたかったので、この場合に関しても、Ficebase Functionsを経由して整形しています。

結果、下記のような構成に落ち着きました。 f:id:anoChick:20171015223844p:plain

やっぱりIFTTTだけだと痒いところに手が届かないので、Cloud Functionsや AWS Lambda等、を使うといい感じになりそうです。

f:id:anoChick:20171016085637p:plain

こういう独特な言い回しは間違えやすい

MRデバイスが家に届いたと思ったらインターネットに繋がらなくなった

悲しすぎるのと、なんか色々と混乱したので整理もかねて書いていく。

 

 

 

26日

午前9時 AcerのMRデバイスが届く

 

午前10時 windows機にMRデバイスを接続し、初期設定中に突然PCがクラッシュ。以降設定ウィザードが進まなくなる。

 

午前10時30分 公式のトラブルシューティングに従い、インストール途中のファイルの削除及びレジストリをいじる。MRデバイスを接続していると、グラボのドライバーが異常停止するようになる。

 

午前11時 グラボのドライバーを最新のものにあげる。windowsじたい画面が常時真っ暗になる。古いドライバーを探してきて事なきを得る。

 

午前12時 同じ現象が起こる人はいないか調べる。microsoft insider previewというものを見つけたのでインストールを試みるも、ダウンロード中に回線が切れる。異常な通信扱いされている様子。ルータをいじり、何度も再接続してアップデートファイルをダウンロードしきる。

 

ここで力尽きて一度寝る。

 

午後5時 リネージュ2をはじめる。楽しい。

 

午後8時 windowsのアップデートをしたら、グラボのドライバーも更新されてしまい、画面が常時真っ暗に。仕方なく強制終了したら、アップデート中だったため、windowsが破損する。回復プログラムを実行する。

 

午後10時 再度microsoft insider previewのアップデートファイルのダウンロードを試みたところ、ルータがクラッシュ。電源後と落ちる。コンセントを指し直したら動いたけど接続情報が飛ぶ。契約時にもらった書類に接続情報が書いてなくて色々調べる。

 

午前12時 諦めてリネージュ2をやる。

 

 

 

 

 

 

 

 

 

もうこのまま引っ越したい。

 

 

 

 

PowerPointの資料生成をRubyで書きたい

資料作成自動化業務ってありますよね。 Googleスライドとか使えばいいのに、ビジネスサイドの理由とかでPowerPointじゃなきゃ嫌だって言われたり、 Railsアプリとして操作したいのにいい感じにPowerPointファイルを扱うgemがなかったりします。

今回はApache POIを使ったをさっと書く。

Apache POI

Apache POI(アパッチ・ポイまたはピーオーアイ)はApacheソフトウェア財団のプロジェクトで、WordやExcelといったMicrosoft Office形式のファイルを読み書きできる100% Javaライブラリとして提供されている。

そういうことなので、これを使えば詰むことはなさそう。

Apache POI - the Java API for Microsoft Documents サンプルコードとかたくさんある。

あとはJNIでrubyJavaをブリッジしてしまえばよい。

JNI

Java Native Interface JVM上で動くコードでネイティブコードで連携するためのインタフェース仕様

f:id:anoChick:20170817121239g:plain

http://rjb.rubyforge.org/

このブリッジを実装したgemがあるので使う。

require 'rjb'
out = Rjb::import('java.lang.System').out
out.println('jarh')

こんな感じでかける。 これでRailsアプリのモデルや何かの資産を活用しつつ、Officeファイルを操作できる。

注意すべき点

RJBはメインスレッド以外のスレッドでの動作はサポートしていない。 うっかりSidekiqのworkerとかで処理しようとするとうまく動かなかったりする。 ジョブキューを使う場合はシングルスレッドで動かすとか、マルチプロセスモデルのものを使うとかしよう。

Firebase Functionsをゲームサーバーにしてみよう。

f:id:anoChick:20170718021908p:plain

Firebase DatabaseとUnityでオンラインゲームを作るとして、 サーバーというか、GlobalManagerロールをどうしようってなったので Firebase Functionsでやってみようと思います。

今回やることは

1分ごとに、 - マップ上にランダムに木を生成する。 - ゲーム内時間を進める。

データ構造

{
  "world": {
    "time": 1,
    "resources": {
      "wood": 0
    },
    "objects": [
      {
        "type": "wood",
        "position": {
          "x":0,
          "y":0,
          "x":0,
        }
      }, {
        "type": "wood",
        "position": {
          "x":2,
          "y":0,
          "x":0,
        }
      }
    ]
  }
}

こんな感じかな。 Functionsでobjectsにwoodを入れていく。

プレイヤーは木を切るとresources.woodを+1する。

やっていく

https://console.firebase.google.com まず適当にFirebaseプロジェクトを作る

npm install -g firebase-tools

firebaseのCLIをインストールして、 適当な空のディレクトリに移動してから

$ firebase init functions

生成されたindex.jsの中は

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.tick = functions.https.onRequest((request, response) => {
  admin.database().ref('/world/time').once("value")
    .then(snapshot => {
      admin.database().ref('/world/time').set(1 + snapshot.val());
    })
  admin.database().ref("/world/objects").push({
    type: 'tree',
    position: {
      x: Math.floor(Math.random() * 51),
      y: 1,
      z: Math.floor(Math.random() * 51)
    }
  });
  response.status(200).end();
});

こんな感じ。

コードの記述が終わったらデプロイをする。

$ firebase deploy --only functions

これでHTTPSのエンドポイントURLが発行される。

定期的に実行する

cron-job.org - Free cronjobs - from minutely to once a year.

これとか使ってみます。 定期的にHTTPリクエストを送ってくれるサービス。 1分毎に叩くように設定してみた。

これで 「1分毎に新たに木が生え、ゲーム内時間が進む」 が実装されました。

時系列データを集めてシェアするサービスを作っている。

Statis αですが公開はしています。

f:id:anoChick:20170717161103p:plain

こういう感じのサービスを作っています。

どういうサービス?

現段階で出来るのは以下のようなことです。

  • APIを用いて時系列データをポストする
  • 対象データのURL・HTMLのパスを指定し、定期的にスクレイピングをさせ、データを収集する
  • シリーズ(時系列データの単位)に対するイベントトリガー(しきい値を超えたらメッセージ)
  • シリーズをまとめて観る事ができる"ボード"
  • ボードへコメントをつける

例えばツイッターの自分のページのフォロワー数を1時間に1度スクレイピングしていくような事ができます。f:id:anoChick:20170717163448p:plain こんな感じに指定すると勝手にデータが入っていく。

あとは しきい値を超えると発火するようなイベントトリガー。 モニタリングツールとかによくあるやつ。 f:id:anoChick:20170717213653p:plain

なんで作ってるの

ビジネスシーンでは、GoogleDataStudio,Redash,Supsersetなどなど、プロジェクトの状況を視覚化する為のBIツールと呼ばれるアプリケーションが増えてきてます。 システム面では、DatadogやStackdriver等のモニタリングツールというものもあり、これも最近増えてきているような気がします。

ただこういったサービスって基本的にプライベートで使う想定ではなく、閉じられた範囲でしか情報を共有しないものです。

一般公開されているデータや個人のデータを1箇所に集約し、横断的に観ることが出来たら、 面白い発見や新しい体験が得られるのではないかと思って作っています。

今後の方針

とりあえずはいろいろなデータを入れて行くことに専念して、 ある程度パターンが増えたらデータの表現方法を考えて、 インフォグラフィックっぽいものが生成される機能とかを作りたいです。

Angular+Firebaseでまずはフロントエンドだけ作っていくとすごく楽しい

タイトルのとおりです。 Angular+Firebaseで一旦それっぽくしちゃうと、モチベーションが下がらないのでおすすめです。

導入

$ npm install -g @angular/cli
$ ng new my-app
$ cd my-app
$ ng serve
** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200 **
Hash: c27678c6699304bb4a70
Time: 7560ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 3.63 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 10.5 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.4 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
webpack: Compiled successfully.

これでhttp://localhost:4200/にアプリケーションがたちました。

それっぽいUIフレームワークをいれる

僕のおすすめは

Angular Material

Clarity Design System

この2つです。 clarityはcardのダサさにウッっとなるのですが、それ以外は無難であることと、デザインガイドラインが用意されているので良いです。 今回はclarityを入れてすすめます。

$ npm install clarity-icons @angular/animations @webcomponents/custom-elements@1.0.0-rc.3 mutationobserver-shim@0.3.2 clarity-ui clarity-angular --save
//.angular-cli.json
      "styles": [
        "../node_modules/clarity-icons/clarity-icons.min.css",
        "../node_modules/clarity-ui/clarity-ui.min.css",
        ...
      ],
      "scripts": [
        "../node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
        "../node_modules/@webcomponents/custom-elements/custom-elements.min.js",
        "../node_modules/clarity-icons/clarity-icons.min.js",
        ...
      ],
...
//src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ClarityModule } from 'clarity-angular'; //追加
import { AppComponent } from './app.component';

@NgModule({
    imports: [
        BrowserModule,
        ClarityModule.forRoot(), //追加
        ....
     ],
     declarations: [ AppComponent ],
     bootstrap: [ AppComponent ]
})
export class AppModule {    }

これでng serveすれば動きます。てきとうに

<!-- src/app/app/component.html -->
<div class="main-container">
  <header class="header header-6">
    <div class="branding">
      <a href="..." class="nav-link">
        <clr-icon shape="vm-bug"></clr-icon>
        <span class="title">僕のWebアプリ</span>
      </a>
    </div>
  </header>
  <div class="content-container">
    <div class="content-area">
      ...
    </div>
    <nav class="sidenav">
      <section class="sidenav-content">
        <a href="..." class="nav-link active">
                    Nav Element 1
                </a>
        <a href="..." class="nav-link">
                    Nav Element 2
                </a>
        <section class="nav-group collapsible">
          <input id="tabexample1" type="checkbox">
          <label for="tabexample1">Collapsible Nav Element</label>
          <ul class="nav-list">
            <li><a class="nav-link">Link 1</a></li>
            <li><a class="nav-link">Link 2</a></li>
          </ul>
        </section>
        <section class="nav-group">
          <input id="tabexample2" type="checkbox">
          <label for="tabexample2">Default Nav Element</label>
          <ul class="nav-list">
            <li><a class="nav-link">Link 1</a></li>
            <li><a class="nav-link">Link 2</a></li>
            <li><a class="nav-link active">Link 3</a></li>
            <li><a class="nav-link">Link 4</a></li>
            <li><a class="nav-link">Link 5</a></li>
            <li><a class="nav-link">Link 6</a></li>
          </ul>
        </section>
      </section>
    </nav>
  </div>
</div>

とか書いておきましょう。それっぽくなると思います。

f:id:anoChick:20170524164453p:plain

ビルドとデプロイ

ビルドは

$ ng build --prod

これだけです。 何処かにデプロイしたいとおもったら、Firebaseが一番早いと思います

$ npm install -g firebase-tools
$ firebase login
$ firebase init



デプロイ先プロジェクトは、既にあればそこを指定し、特になければ[create a new project]で新たに作ってください。

✔  Firebase initialization complete!

となれば成功。

//firebase.json
{
  "hosting": {
    "public": "dist"
  }
}

と書き加えてから

$ firebase deploy

とコマンドを叩くとデプロイされます。

=== Deploying to 'development-ef09c'...

i  deploying hosting
i  hosting: preparing dist directory for upload...
✔  hosting: 8 files uploaded successfully
i  starting release process (may take several minutes)...

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/development-ef09c/overview
Hosting URL: https://development-ef09c.firebaseapp.com

ログインとかユーザ管理のしくみもFirebaseに乗っかるとすごい楽なんですが、 それはまた別の話。

RailsでGraphQLを使う

結構使いやすくなっていたので紹介

セットアップ

# Gemfile
gem 'graphql'
rails generate graphql:install

まず導入 基本的な構成と、graphiql(GraphQLのクライアントをRailsにマウントするやつ)が入る

http://localhost:3000/graphiql

サンプルフィールドが用意されているのでクエリを叩くと、結果が得られる。 f:id:anoChick:20170521182758p:plain

こんなかんじ。

ActiveRecordとつなげる

Railsで使うからにはGraphQLのObjectとRailsのモデルを繋げなければいけません。 つなぐために

GitHub - goco-inc/graphql-activerecord

このGemを使います。

rails g model Book name:string

こんなモデルを予め作っておいてください。

# Gemfile
gem 'graphql-activerecord'
bundle install

レコード作成

まずはBookモデルに対応したBookTypeを作ります。

# app/graphql/types/book_type.rb
Types::BookType = GraphQL::ObjectType.define do
  name "Book"
  backed_by_model :books do
    attr :name
  end
end

次にBookを新たに作成するためのMutationを定義します。

# app/graphql/mutations/create_book.rb
Mutations::CreateBook = GraphQL::Relay::Mutation.define do
  name "CreateBook"

  return_field :book, Types::BookType
  input_field :name, !types.String

  mutator_definition = GraphQL::Models.define_mutator(self, Book, null_behavior: :leave_unchanged) do
    attr :name
  end
  resolve ->(obj, args, ctx) {
    { book: Book.create(name:args[:name]) }
  }
end

あとは、このMutationを

App名Schemaに認識させます。

# app/graphql/types/mutation_type.rb

Types::MutationType = GraphQL::ObjectType.define do
  name 'Mutation'
  field :CreateBook, field: Mutations::CreateBook.field
end
# app/graphql/アプリ名_schema.rb

アプリ名Schema = GraphQL::Schema.define do
  query(Types::QueryType)
  mutation(Types::MutationType) # 追加

  lazy_resolve(Promise, :sync)
  instrument(:query, GraphQL::Batch::Setup)
  instrument(:field, GraphQL::Models::Instrumentation.new)
end

これで叩ける用にはなりました。

f:id:anoChick:20170521194025p:plain 問題なければDBにレコードが作成されているはずです。

queryを使う。

先程BookTypeは作ってしまったので、QueryTypeを記述するだけです。

# app/graphql/types/query_type.rb

Types::QueryType = GraphQL::ObjectType.define do
  name "Query"
  
  field :book, Types::BookType do
    description "An example field added by the generator"
    argument :id, !types.ID
    resolve ->(obj, args, ctx) {
      Book.find(args[:id])
    }
  end
end

f:id:anoChick:20170521194524p:plain

関連レコードも引っ張ってくる

これだけだとつまらないので関連データもとってこれるようにしたいと思います。 デモ用モデルとして「登場人物」を新たに作ります。

rails g model Character name:string book:references

次に CharacterTypeを定義します。 ちなみに、

rails g graphql:object Character

で作ることも出来る。

# app/graphql/types/book_type.rb
Types::BookType = GraphQL::ObjectType.define do
  name "Book"
  backed_by_model :books do
    attr :name
    has_many_array :characters # 追加
  end
end

今日の雑記はgraphql-activerecord - あのにのに

でも書いたけど Typeの定義の仕方によっては一手間必要・

f:id:anoChick:20170521213806p:plain

できたできた。