はじめに
たまの音楽番組で好きなアーティストのライブだけ見たいという要望があったので(僕)、見逃し防止Pushツールをつくりました。
キーワード
・firebase
・ServiceWorker
・golang(beego)
・サブドメイン
成果物
好きなミュージシャンの出演タイミングをプッシュ通知してくれるサービスかんせい!https://t.co/IFsfP9MW2P pic.twitter.com/MpfCSRKM7U
— playtag551 (@playtag551) March 13, 2020
試しに「脱力タイムズ」で試した動画が↑にでてるはず。
うまく反応しました(^^)
バックグラウンドでも動作するのがPWAのいい所!
逆にiphoneで利用できないのがつらい点です。
swiftでアプリにして、apple storeに登録しよ(macないけど)
イメージ
前提
・TwitterAPIの登録
・firebaseの登録
・golangでのサーバ起動
・ドメイン取得済み
TwiiterAPIとの連携
Go言語でTwitterAPIを利用する(anacondaの利用)
golang側からTwitterAPIを叩きます。
取得したツイートに特定のキーワードが含まれているかを確認し、プッシュ通知を送るか否かを判定します。
// 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で送受信しています。
// 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)
}
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
);
});
<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)もヨロシクオネガイシマス!