リンク短縮サービスをお手軽に作る

# なぜリンク短縮サービス？
私はよく、[bit.ly](https://app.bitly.com/)を使ってリンクを短縮していました。しかしbit.lyにより生成されるリンクはhttpsに対応しておらず、bit.lyリンクが含まれているメールがGmailにはじかれてしまう、という事象が周囲で発生してしまいました。

そこで、コミュニティの性質上、そこそこ長いURLをメールで送信する機会が多いので、Gmailにはじかれない安全なリンク短縮サービスを自作する必要が生じたのです。

システム系の開発経験はほぼゼロなので、なにか間違ったことを書いていたらすみません。

# step 1: firebase側の準備
## 1-1: プロジェクトの作成
[firebaseコンソール](https://console.firebase.google.com/)にアクセスし、新しいプロジェクトを作成します。

![スクリーンショット (536).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481386/644324d4-03e3-be70-b85b-62ba983daffa.png)


- プロジェクト名が公開されることはありませんので、適当につけます。
- 今回作成する形式のサービスにおいては、Google Analyticsが使えませんので有効化しなくてもかまいません。
- アプリを作成する必要はありません。

## 1-2: Dynamic Linkの設定
コンソール画面に戻り、短縮リンク（Dynamic Link）の設定に移りましょう。

![スクリーンショット (538).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481386/897a02a4-cedb-8337-549a-c589b9772bcd.png)


- 左側のメニューから、「拡大」>「Dynamic Link」と進みます。
- 開いたページで「始める」をクリックします。
- URL接頭辞の追加では、短縮リンクの冒頭の文字列を指定できます。カスタムドメインにすることもできるようですが、ここでは無料で利用できるFirebaseのドメイン「xxxxx.page.link」（xxxの部分を指定できる）を利用します。
- 「xxxxx.page.link」形式のドメインを使用する場合、これで設定は終わりです。

## 1-3: APIキーの発行
再びコンソールに戻り、左側のメニューの一番上「Project Overview」の隣にある歯車マーク>プロジェクトの設定 と進みます。表示されたページにあるウェブAPIキーをコピーします。

# step 2: Google Apps Script側の準備（.gsファイルの準備）
## プロジェクトの作成
[Google Apps Scriptのプロジェクト管理画面](https://script.google.com/)にアクセスし、新規プロジェクトを作成します。

## APIキーをプロパティに登録する
プロジェクトを適当な名前で保存したら、まず先ほどのAPIキーをプロパティに登録します。.gsファイルは、共有しない限り誰かに見られることはありませんから、APIキーをスクリプトファイルにベタ書きしてもよいのですが、技術者間でスクリプトの共有などをする可能性があることも考え、tokenの類はプロパティに登録するほうが一般的なようです。

- 画面左上のファイルメニューから「プロジェクトのプロパティ」>「スクリプトのプロパティ」と進みます。
- 行を追加すると、新しいプロパティを追加できます。
- 「名前」に適当な名前をつけ、値に先ほどコピーしたAPIキーの値を貼ります。

このように登録したスクリプトプロパティの値は、.gsファイル内で次のようにすれば取得できます。

```js
var property_value = PropertiesService.getScriptProperties().getProperty("property_name");
```

## fetchするスクリプトを書く
こんな感じで、短縮リンクを取得する函数を定義しましょう。引数は短縮前のURLとします。

```js
function getShortURL(url) {
    var api_key = PropertiesService.getScriptProperties().getProperty("API_KEY"); // プロパティ名として先ほど登録したAPIキーの名前を入れます
    var domain = "xxxxx.page.link"; // 先ほど設定した

    var apiUrl = "https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=" + api_key;

    var payload = {
        dynamicLinkInfo: {
            dynamicLinkDomain: domain,
            link: url,
        },
        suffix:{
            option:"SHORT"
        }
    };

    var option = {
        method: 'POST',
        contentType: 'application/json',
        payload: JSON.stringify(payload),
        muteHttpExceptions: true,
    };

    var received = UrlFetchApp.fetch(apiUrl, option);

    return JSON.parse(received).shortLink;
}
```

シンプルですからあまり解説するところもないのですが、``suffix:{option: "SHORT"}``の部分で、実際の短縮リンクにおいて``https://xxxxx.page.link/``のあとに続く文字列の長さを指定しています。

- SHORTにすると、ランダムな4文字になります。
- そもそもoptionを指定しない、あるいはUNGUESSABLEにすると、ランダムな17文字になります。

## テスト実行&承認
定義した函数を選択し、▶マークを押すと函数を実行できます（適当なurlを変数宣言してテスト実行してみてください）。

- 初回の実行で承認を求められますので、承認しましょう。

# step 3: Google Apps Script側の準備（.htmlファイルの準備）
ここまでの準備で、すでにGoogle Apps Script側から短縮リンクを作成することはできるようになりました。ここから先は、開発者以外のユーザーが使いやすいような簡単なUIを作るパートです。

## HTMLファイルの作成
Google Apps Scriptのプロジェクト画面で、ファイルメニューから「New」>「HTMLファイル」と進みます。``index.html``みたいな名前をつけて保存しましょう。

とりあえずコードを貼ります。

```html:index.html
<!DOCTYPE html>

<html lang="ja">

<head>
    <meta charset="utf-8">
    <title>
        リンク短縮サービス
    </title>
    <script>
        // ボタンを押したときに実行される函数
        function shorten() {
            document.getElementById("loading").innerHTML = "リンクを短縮中です……"
            // 入力された文字列を取得する　　
            const input = document.getElementById("input").value;
            if (input != "") { // 何かしら入力されている場合
                // .gsファイル内の函数を実行する
                google.script.run.withSuccessHandler(e => {
                    document.getElementById("shorten").value = "";
                    if (e == undefined) {
                        document.getElementById("shorten").value = "";
                        document.getElementById("loading").innerHTML = "有効なリンクを入力し、再度短縮ボタンを押してください。"
                    } else {
                        document.getElementById("shorten").value = "";
                        document.getElementById("shorten").value = e;

                        // 自動でクリップボードにコピーする
                        document.getElementById("shorten").select();
                        document.execCommand("Copy");

                        document.getElementById("loading").innerHTML = "短縮完了<br>リンクをクリップボードにコピーしました"
                    }
                }).getShortURL(input);
            } else {
                document.getElementById("shorten").value = "";
                document.getElementById("loading").innerHTML = "リンクを入力し、再度短縮ボタンを押してください。";
            }
        }
    </script>
</head>

<body>
    <h2 style="color:#52B6A7; margin-bottom:10px;">
        リンク短縮サービス
    </h2>
    <div class="canvas">
        <div>
            <textarea id="input" cols="100" rows="3"></textarea>
        </div>
        <a class="btn" onclick="shorten();">リンクを短縮する</a>
        <p id="loading"></p>
        <input type="text" id="shorten">
    </div>
</body>

<style>
    .canvas {
        display: block;
    }

    #input {
        margin-bottom: 20px;
    }

    .btn {
        margin: 5px 0px 5px 0px;
        text-decoration: none;
        padding: 10px;
        color: #52B6A7;
        border: solid 1.5px #52B6A7;
    }

    .btn:hover {
        color: white;
        background: #52B6A7;
        transition: background-color 0.5s ease;
    }

    #loading {
        color: #52B6A7;
        margin-bottom: 10px;
    }

    #shorten {
        margin-top: 10px;
    }
</style>

</html>
```

CSSの部分はただのCSSですし、「短縮中です……」みたいなメッセージを表示する部分もただのDOM操作なので、細かい説明は省きますが、仕組みとしては、

1. textareaに入力された文字列を、DOM操作（document.getElementById().value）で取得します
2. 先ほど定義した函数にその文字列を渡して、短縮リンクを取得します
3. 短縮リンクが帰ってきたら、結果表示欄に表示するとともに、自動でクリップボードにもコピーします

という塩梅になっています。

### google.script.runオブジェクト
ご存じの方は飛ばしていただきたいのですが、私はここの処理を知らなかったので少し躓きました。そもそも、Google Apps Scriptでhtmlファイルを作成すると、

- htmlファイル（scriptに書いたjsも当然含む）は、もちろんユーザー側のブラウザで読み込まれます
- 一方、.gsファイルは依然Googleのサーバー上にあり、ユーザー側では読み込まれません

したがって、Googleのサーバー上にある.gs上の函数を実行するための方法が用意されています。それが``google.script.run``です。

.gs側で引数``arg``をとる``func``という函数を定義したとします。このとき、これをhtml側から呼び出して実行するためには、次のようにします。

```js
google.script.run.func(arg);
```

また、この函数のreturnする値をブラウザサイドで受け取るためには、google.script.runオブジェクトに用意されている``withSuccessHandler``メソッドを利用します。このメソッドは.gsの函数が返した値を引数とするcallback函数を引数に取ります。

今回の例であれば、.gs上の``getShortUrl``函数で短縮リンクを生成して返し、それをページ上に表示したいわけですから、

```js
google.script.withSuccessHandler(shortUrl=>{ // callbackの引数はgetShortUrlの戻り値
    // 短縮されたURLを用いた処理...
}).getShortUrl(url);
```

とすればよいことになります。

### バリデーションについて
入力されたURLが有効なURLかを正規表現などでチェックしてもよいのでしょうが、面倒だったので、いったん短縮してみて、``undefined``が返されたら有効じゃなかったよというメッセージを表示する、という仕様にしています。

## .gsに書き足す
Google Apps Scriptでhtmlファイルを作る場合には、.gsに以下のコードが書いてある必要があります。お決まりなので私はいつもとりあえずコピペしてから始めるようにしています。

```js
function doGet(e) {
    var document = HtmlService.createTemplateFromFile("index");
    return document.evaluate(); 
}
```

今回の記事においては本筋とは外れるので、これ以上は言及しないことにします。

# 公開する
- Google Apps Scriptのプロジェクト画面で、「公開」メニュー>ウェブアプリケーションとして公開 と進みます。
- Project Versionとして「New」を、Who has access to the appとして適切なものを選びましょう。Execute the app asで「User accessing the web app」を選ぶと、各ユーザーに対して承認が求められるので、基本的には「Me」でいいんじゃないかと思います。
- 設定がすんだら「公開」しましょう。

# おまけ
- fetchの際に渡すjsonの形式は、[公式リファレンス] (https://firebase.google.com/docs/reference/dynamic-links/link-shortener?hl=ja)に記載がありますので、参考になさってください。
- もっとちゃんと設定すればクリック数の統計とかを取れる気はします。今回はそこまでの必要がなかったので、短縮するだけになっています。