Firebaseの無料枠でふぁぼを拡張した

「神絵師のツイートをふぁぼってブックマークしていたのにTwitterのAPIは3600件しか遡れない...」という悲しい気持ち、誰しも一度は抱いたことがあると思います。僕は抱きました。そこで、自前でふぁぼを保存することにしました。

## 概要
- Firebaseにツイートを保存する仕組みを作った。
- スマホに保存しているといつ死ぬか分からないので、Firebaseを利用してオンラインで保存することにした。

ただし、いろいろ問題が発生するので添付された画像や動画は保存しません。（直リンクは保存する）

## 構成

クライアントサイドは、ちょうど自前でAndroid用Twitterクライアントを作って使っていたので、そこにボタンを追加してサーバーに投げる処理を実装しました。
サーバー側は普通に借りるとお金がかかりますが、無料でも十分使えるFirebaseという神サービスがあるのでこれを使うことにしました。

- 無課金に優しい
    - Cloud functions（簡単にAPIを設置できるやつ） 12.5万/月が無料
    - Cloud firestore（簡単にDBを設置できるやつ） 容量1 GiB/月、数万アクセス/日が無料
    - その他いっぱい無料 https://firebase.google.com/pricing?hl=ja
- Firebaseというフレームワークの中で、最小限のコーディング量でAPI、DBの設置を含む大抵のことができる
- 任意のプラットフォーム向けのSDKが充実している 

神サービスです。

## 実装

### DB

Cloud Firestoreを使いました。ボタンをポチポチするだけで定義できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184595/05585327-5001-8d77-5cad-27f70de48bca.png)

コレクションはドキュメントの集合です。
1ツイートのデータを1ドキュメントとして保存するコレクション`pics`と、保存総数をカウントするためのドキュメントを1つだけ持つコレクション`info`を設定しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184595/bdea351e-d52d-1b7f-66f9-52ae5497be11.png)


### API

Cloud Functionsを使いました。コマンドを打つだけですぐに外部に公開できます。Cloud Firestoreへのアクセスも簡単です。

ただ保存するだけでなく、同じアカウントのツイートを何件ふぁぼっているかをクライアントへ返すようにしました。
これにより、「...こんなに素晴らしい絵を描く人なのにまだ2件しかふぁぼできていない...？」という気づきを生み、すぐに遡りを開始することができます。

```javascript

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

// エンドポイント /save を設置
exports.save = functions.https.onRequest((request, response) => {
    const {url,tweet_id,user_id,text,tweet_link,screen_name,status} = request.body 

    //認証
    if(request.get('Authentication')!=="*********"){
        response.send("none")
        return
    }

    //ツイートが保存されるコレクション
    const pics = db.collection('pics')
    //保存総数をカウントするためのコレクション
    const statistics = db.collection('info').doc('statistics')

    //tweet_idで検索し、既に保存していないか確認する
    pics.where('tweet_id','==',tweet_id).get()
        .then(snapshot=>{
            //同じユーザーの保存済みツイート数を確認する
            pics.where('user_id','==',user_id).get().then(user_pics =>{
                if(snapshot.size!==0){
                    // 既に保存済みならその旨を「同じユーザーの保存済みツイート数」とともにメッセージとして返す
                    response.send(`既にあります。全${user_pics.size}件`)                
                }else{
                    //追加
                    pics.add(
                        {
                            url:url,
                            tweet_id:tweet_id,
                            tweet_id_str:status.id_str,
                            tweet_link:tweet_link,
                            user_id:user_id,
                            text:text,
                            status:status,
                            screen_name:status.user.screen_name,
                            timestamp: admin.firestore.FieldValue.serverTimestamp(),
                        })
                    statistics.update("count",admin.firestore.FieldValue.increment(1))
                    // 「同じユーザーの保存済みツイート数」を返す
                    response.send(`追加しました。${user_pics.size+1}件目`)
                }
                return;
            }).catch(err=>{console.log(err)})   
            return
        }).catch(err=>{console.log(err)})   
});
```

### クライアント

まずふぁぼってから、上記のAPIにデータを投げ、レスポンスを表示します。

```java
public static void save_status(Context context, account, TwitterStatus status){

    // とりあえずふぁぼる
    account.favorite(status, true, context);

    // 通信用スレッドを立ち上げてサーバーに投げる
    Handler handler = new Handler();
    new Thread(()->{
        try {
            // データの準備
            JSONObject json = new JSONObject();
            json.put("url",new JSONArray(status.getMedia().getURLs()));
            json.put("tweet_id",status.getId());
            json.put("user_id",status.getUser().getId());
            json.put("text",status.getTextWithoutMediaLink());
            json.put("tweet_link",status.getLink());
            json.put("screen_name",status.getUser().getScreenName());
            json.put("status",status.getJSON());
            
            // /save へ送信
            URL url = new URL("https://us-central1-********.cloudfunctions.net/save"); //用意したエンドポイント
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            connection.setRequestProperty("Authentication","************");
            connection.setRequestProperty("Content-Type","application/json");
            connection.setRequestMethod("POST");
            PrintStream ps = new PrintStream(connection.getOutputStream());
            ps.print(json.toString());
            ps.close();
            
            //レスポンスを表示
            String response = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
            handler.post(()->{
                Toast.makeText(context, response+"\n"+description, Toast.LENGTH_SHORT).show();
            });
        } catch (MalformedURLException ignored) {
        } catch (IOException ignored) {
        } catch (JSONException ignored) {
        }
    }).start();
}
```

## ウェブ

DBをの中身を一覧表示して自分で見るためのウェブサイトをCloud Hostingしましたが卒論がやばいので割愛します。

## その他

Cloud Firestoreは、ドキュメントをそのフィールドを使って検索したり、並び替えたりすることができます。

試しにリスイン数で並び替えてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184595/d73da3ac-a432-fcdf-4b42-c69ffed8c76d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184595/d4db35f6-5d8c-87c0-00db-d974f0f6024f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184595/a82ae9ba-1c87-0f8f-a28c-613f8ed428b5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184595/886c9aa9-ba57-07c5-b927-a1f54da47b91.png)

以上です