2
2

More than 3 years have passed since last update.

Mステで好きなアーティストを見逃さないためのプッシュ通知サービス

Last updated at Posted at 2020-03-16

はじめに

たまの音楽番組で好きなアーティストのライブだけ見たいという要望があったので(僕)、見逃し防止Pushツールをつくりました。

キーワード

・firebase
・ServiceWorker
・golang(beego)
・サブドメイン

成果物

livepush(ライブプッシュ)


試しに「脱力タイムズ」で試した動画が↑にでてるはず。
うまく反応しました(^^)


バックグラウンドでも動作するのがPWAのいい所!
逆にiphoneで利用できないのがつらい点です。
swiftでアプリにして、apple storeに登録しよ(macないけど)

イメージ

嵐の曲だけ見たいとき
20200315_184146000_iOS.png
詳細
20200315_185911000_iOS.png

前提

・TwitterAPIの登録
・firebaseの登録
・golangでのサーバ起動
・ドメイン取得済み

TwiiterAPIとの連携

Go言語でTwitterAPIを利用する(anacondaの利用)
golang側からTwitterAPIを叩きます。
取得したツイートに特定のキーワードが含まれているかを確認し、プッシュ通知を送るか否かを判定します。

controller.go
// twitter-apiに接続
func GetTwitterApi() *anaconda.TwitterApi {
    api := anaconda.NewTwitterApiWithCredentials(beego.AppConfig.String("your-access-token"), beego.AppConfig.String("your-access-token-secret"), beego.AppConfig.String("your-consumer-key"), beego.AppConfig.String("your-consumer-secret"))
    return api
}

// ツイートを直近1000件分取得する
func GetTwitter(keyword string) anaconda.SearchResponse {
    api := GetTwitterApi()
    v := url.Values{}
    v.Set("count", "1000")
    searchResult, _ := api.GetSearch(keyword, v)
    return searchResult
}

// 取得したツイートを厳選する
func NormalizeTwitter(searchResult anaconda.SearchResponse) (res []anaconda.Tweet) {
    for _, tweet := range searchResult.Statuses {
        // リツイートは除外
        if tweet.RetweetedStatus == nil {
            t, _ := time.Parse("Mon Jan 2 15:04:05 -0700 2006", tweet.CreatedAt)
            // タイムスタンプの整形
            sec := time.Since(t).String()
            sec = strings.Split(sec, ".")[0]
            tweet.CreatedAt = sec + "s ago"
            res = append(res, tweet)
        }
    }
    return res
}

// push通知判定用ツイートを抽出
func judgeTwitter(searchResult []anaconda.Tweet) (res []anaconda.Tweet) {
    // 最新30秒のツイートを対象
    delayTime := 30
    for _, tweet := range searchResult {
        sec := strings.Split(tweet.CreatedAt, "s")[0]
        if !strings.Contains(sec, "m") {
            secInt, _ := strconv.Atoi(sec)
            if secInt < delayTime {
                res = append(res, tweet)
            }
        }
    }
    return res
}

// プッシュ通知を送信するか判定
func JudgePush(searchResult []anaconda.Tweet) bool {
    n := 0
    threshold := 2
    // 「欅坂はじまったよ!!」みたいなツイートに反応
    Judgewords := [...]string{"始まり", "はじまり", "始まっ", "はじまっ", "始まる", "はじまる", "きそう", "来そう", "きた", "来た", "キタ", "もうすぐ"}
    for _, tweet := range searchResult {
        for _, word := range Judgewords {
            if strings.Contains(tweet.FullText, word) {
                n++
                break
            }
        }
    }
    fmt.Println("hit count:", n)
    var push bool = false
    if n >= threshold {
        push = true
    }
    return push
}

Push通知

Progressive Web Apps (PWA) 学習者のメモ その2 (プッシュ通知とFCM)

golangのサーバがfirebaseにリクエストを投げており、firebaseがTokenの登録されているServiceWorkerにプッシュ通知を送信しているようです。
なお、TokenはクライアントからxmlhttprequestのPOSTで送受信しています。

controller.go
// tokenベースでpush通知を送信する
func SendToToken(RegistrationToken string, SearchWord string) {
    ctx := context.Background()
    opt := option.WithCredentialsFile("/serviceAccountKey.json")

    app, err := firebase.NewApp(ctx, nil, opt)
    if err != nil {
        log.Fatalln(err)
        return
    }

    client, err := app.Messaging(ctx)
    if err != nil {
        log.Fatalln(err)
        return
    }

    message := &messaging.Message{
        Notification: &messaging.Notification{
            Title: SearchWord + " will start !!",
            Body:  "そろそろ始まりそうですよ。",
        },
        Token: RegistrationToken,
    }

    response, err := client.Send(ctx, message)
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println("Successfully sent message:", response)
}

firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/5.10.1/firebase-app.js');
importScripts(
  'https://www.gstatic.com/firebasejs/5.10.1/firebase-messaging.js'
);

self.addEventListener('fetch', function(event) {});

firebase.initializeApp({
  messagingSenderId: '1018●●●●●●●●'
});

const messaging = firebase.messaging();
//バックグラウンドでのプッシュ通知の受信
messaging.setBackgroundMessageHandler(function(payload) {
  console.log(
    '[firebase-messaging-sw.js] Received background message ',
    payload
  );
  // Customize notification here
  var notificationTitle = payload.notification.title; // タイトル
  var notificationOptions = {
    body: payload.notification.body, // 本文
    icon: payload.notification.icon // アイコン
  };

  return self.registration.showNotification(
    notificationTitle,
    notificationOptions
  );
});

index.tpl
<script>
// フォアグラウンドでのプッシュ通知の受信
messaging.onMessage(payload => {
        // console.log('Message received. ', payload);
        var notificationTitle = payload.notification.title; // タイトル
        var notificationOptions = {
          body: payload.notification.body, // 本文
          icon: payload.notification.icon // アイコン
        };

        showNotification(notificationTitle, notificationOptions);
      });
</script>

サブドメインにリバースプロキシ

conohaのVPS + nginx+お名前.comで取得したドメイン&サブドメインの設定
ここでは、steveltn/https-portal:1によるdockerコンテナがすでに起動しており、今回作成したWebアプリコンテナで公開中のポートに接続するようにします。

注意点としては、同一のdocker-compose.yml内での接続方法は、
docker で全自動 Let's encrypt のようにアプリ名をlinksして接続すればよいのですが(このときポート開放は127.0.0.1:8080:8080でも、8080:8080でもよい)、

別々のdocker-compose.ymlの場合は、ポートを8080:8080のようにフルアクセスできるようにし、ファイアーウォールも許可する必要がありました。
さらに、https-portalコンテナでは、DOMAINS内で、livepush.shijimi.work -> http://dockerhost:8080 のようにhostPCのポートを指定する必要があります。
例えば、ここでhttp://127.0.0.1:8080 のように設定しても良さそうなものですが、おそらくlocalhostがhttps-portalコンテナのことと捉えられている気がします。

まとめ

想像以上にfirebase周りで苦戦しました。
これで、ゲリラ系の放送には対応できそうです!!
前回の記事で紹介したドラマ・映画のSNS(shijimi)もヨロシクオネガイシマス!

2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2