Softbank光のBBフォンの通話料を計算するツールを作った

くっそどうでもいいですが最近家の050ひかり電話用に安い電話機を買いました。
よく親が実家に電話をかけるのですがSoftbank/Y!mobileの22円/30秒よりBBフォンというSoftbank光に入っていてかつ光BBユニットがある人なら無料で使える050のサービスのほうが8.789円/3分と本当に同じ会社がやっているのかと疑うほど安いので変えたのですが、

ワイ> なんならいくら安くなったかわかるツール作ったろ！

っと思い作ってみました。

# 実物
多分見てもらったほうが早いと思うので。

https://apps.ogatomo.net/BBphone-App/index.html

# 使い方
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518724/83689533-a773-9a7e-34b1-6a564852d80d.png)
起動するとこんな感じです。
電話してるときに計れるようにストップウォッチ機能もつけてみました。
計ったあとは転送を押せば下のとこに入力され、「一般電話」「携帯①」などを押せば料金が計算されます。
ついでにいくらお得かも出ます(一様SB/YMの22円/30秒を基準にしています)

:::note warn
このツールではSB間は無料と表記していますが**無料の「ホワイトコール24」に加入した場合です。**
加入していない場合は**普通の携帯の料金がかかるので注意してください。**
**(※ホワイトコール24は店舗からのみ加入可能です。)**
:::

Bulmaを使っているのでUIはかなりそれっぽいと思います。

# ソースコード
~~~html:index.html
<html>

<head>
    <title>BBフォン通話料計算アプリ</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
    <script src="bbphone.js"></script>
    <script src="stopwatch.js"></script>

    <meta charset="UTF-8">
    <meta name="msapplication-square70x70logo" content="./site-tile-70x70.png">
    <meta name="msapplication-square150x150logo" content="./site-tile-150x150.png">
    <meta name="msapplication-wide310x150logo" content="./site-tile-310x150.png">
    <meta name="msapplication-square310x310logo" content="./site-tile-310x310.png">
    <meta name="msapplication-TileColor" content="#0078d7">
    <link rel="shortcut icon" type="image/vnd.microsoft.icon" href="./favicon.ico">
    <link rel="icon" type="image/vnd.microsoft.icon" href="./favicon.ico">
    <link rel="apple-touch-icon" sizes="57x57" href="./apple-touch-icon-57x57.png">
    <link rel="apple-touch-icon" sizes="60x60" href="./apple-touch-icon-60x60.png">
    <link rel="apple-touch-icon" sizes="72x72" href="./apple-touch-icon-72x72.png">
    <link rel="apple-touch-icon" sizes="76x76" href="./apple-touch-icon-76x76.png">
    <link rel="apple-touch-icon" sizes="114x114" href="./apple-touch-icon-114x114.png">
    <link rel="apple-touch-icon" sizes="120x120" href="./apple-touch-icon-120x120.png">
    <link rel="apple-touch-icon" sizes="144x144" href="./apple-touch-icon-144x144.png">
    <link rel="apple-touch-icon" sizes="152x152" href="./apple-touch-icon-152x152.png">
    <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon-180x180.png">
    <link rel="icon" type="image/png" sizes="36x36" href="./android-chrome-36x36.png">
    <link rel="icon" type="image/png" sizes="48x48" href="./android-chrome-48x48.png">
    <link rel="icon" type="image/png" sizes="72x72" href="./android-chrome-72x72.png">
    <link rel="icon" type="image/png" sizes="96x96" href="./android-chrome-96x96.png">
    <link rel="icon" type="image/png" sizes="128x128" href="./android-chrome-128x128.png">
    <link rel="icon" type="image/png" sizes="144x144" href="./android-chrome-144x144.png">
    <link rel="icon" type="image/png" sizes="152x152" href="./android-chrome-152x152.png">
    <link rel="icon" type="image/png" sizes="192x192" href="./android-chrome-192x192.png">
    <link rel="icon" type="image/png" sizes="256x256" href="./android-chrome-256x256.png">
    <link rel="icon" type="image/png" sizes="384x384" href="./android-chrome-384x384.png">
    <link rel="icon" type="image/png" sizes="512x512" href="./android-chrome-512x512.png">
    <link rel="icon" type="image/png" sizes="36x36" href="./icon-36x36.png">
    <link rel="icon" type="image/png" sizes="48x48" href="./icon-48x48.png">
    <link rel="icon" type="image/png" sizes="72x72" href="./icon-72x72.png">
    <link rel="icon" type="image/png" sizes="96x96" href="./icon-96x96.png">
    <link rel="icon" type="image/png" sizes="128x128" href="./icon-128x128.png">
    <link rel="icon" type="image/png" sizes="144x144" href="./icon-144x144.png">
    <link rel="icon" type="image/png" sizes="152x152" href="./icon-152x152.png">
    <link rel="icon" type="image/png" sizes="160x160" href="./icon-160x160.png">
    <link rel="icon" type="image/png" sizes="192x192" href="./icon-192x192.png">
    <link rel="icon" type="image/png" sizes="196x196" href="./icon-196x196.png">
    <link rel="icon" type="image/png" sizes="256x256" href="./icon-256x256.png">
    <link rel="icon" type="image/png" sizes="384x384" href="./icon-384x384.png">
    <link rel="icon" type="image/png" sizes="512x512" href="./icon-512x512.png">
    <link rel="icon" type="image/png" sizes="16x16" href="./icon-16x16.png">
    <link rel="icon" type="image/png" sizes="24x24" href="./icon-24x24.png">
    <link rel="icon" type="image/png" sizes="32x32" href="./icon-32x32.png">
    <link rel="manifest" href="./manifest.json">

</head>

<body>
    <nav class="navbar is-link" role="navigation" aria-label="main navigation">
        <div class="navbar-brand">
            <a class="navbar-item" href="https://bulma.io">
                <p>BBフォン通話料計算アプリ</p>
            </a>
        </div>
    </nav>

    <div class="m-5">
        <p>BBフォンの通話料を簡単に計算します。</p><br>
        <div class="title is-1" id="time" style="text-align: center;">00:00</div>
        <div class="container has-text-centered">
            <div class="card mx-auto" style="width:400px">
                <div class="card-content">
                    <button id="start" class="button is-primary is-small is-rounded">スタート</button>
                    <button id="stop" class="button is-danger is-small is-rounded" disabled>ストップ</button>
                    <button id="reset" class="button is-warning is-small is-rounded" disabled>リセット</button>
                    <button id="transform" class="button is-link is-small is-rounded" disabled>転送</button>
                </div>
            </div><br>
        </div>

        <div class="container has-text-centered">
            <div class="card mx-auto" style="width:600px">
                <div class="card-content">
                    <div class="field">
                        <div class="control" style="display: flex; justify-content: center; align-items: center;">
                            <input id="min" type="number" class="input" style="width: 5em;">
                            <span style="margin: 0 0.5em;">分</span>
                            <input id="sec" type="number" class="input" style="width: 5em;">
                            <span style="margin: 0 0.5em;">秒</span>
                        </div>
                    </div>
                    <hr>
                    <button id="type1" class="button is-primary m-3 is-rounded">一般電話</button>
                    <button id="type2" class="button is-primary m-3 is-rounded">携帯①</button>
                    <button id="type3" class="button is-primary m-3 is-rounded">携帯②</button>
                    <button id="type4" class="button is-primary m-3 is-rounded">SB系</button><br>
                    <a id="12">>>携帯①と②の違いはこちら</a>
                </div>
            </div><br>
        </div>
        <hr>
        <p id="call">今回発生した通話料は...<br>
            <b><span id="ryokin">-</span> 円</b>です！
        </p><br>
        <p>普通に通話するよりも <b><span id="toku">-</span> 円お得！</b></p>
        <hr>
        <p class="title">料金表</p>
        <div class="content">
            <ul>
                <li>一般電話(0AB-J)<br>8.789円/3分</li>
                <li>携帯①(22:00~翌07:59)<br>22円/分</li>
                <li>携帯②(08:00~21:59)<br>27.5円/分</li>
            </ul>
        </div>
    </div>
</body>

</html>
~~~
(Favicon用のコードも混じっているので使わない場合は消してください。下のサイトでつくりました。ついでに全画面で出すだけのPWAにも対応してます)

https://ao-system.net/favicongenerator/

~~~js:bbphone.js
// 読み込み完了まで待機
document.addEventListener("DOMContentLoaded", function () {
    // ボタン取得
    var type1 = document.getElementById("type1");
    var type2 = document.getElementById("type2");
    var type3 = document.getElementById("type3");
    var type4 = document.getElementById("type4");
    var k12 = document.getElementById("12");

    // テキストspan取得
    var ryokin = document.getElementById("ryokin");
    var toku = document.getElementById("toku");

    // 入力欄取得
    var min = document.getElementById("min");
    var sec = document.getElementById("sec");

    // 一般電話
    type1.addEventListener("click", function () {
        var min_v = min.value;
        var sec_v = sec.value;

        if (min_v == "") {
            min_v = 0;
        }

        if (sec_v == "") {
            sec_v = 0;
        }

        var totalSeconds = parseFloat(min_v) * 60 + parseFloat(sec_v);
        //console.log(totalSeconds)
        // 3分ごとの通話料を計算
        var cost = Math.ceil(totalSeconds / 180) * 8.789;

        ryokin.innerText = cost
        c3022(totalSeconds, cost)
    });

    // 携帯1(22:00~07:59)
    type2.addEventListener("click", function () {
        var min_v = min.value;
        var sec_v = sec.value;

        if (min_v == "") {
            min_v = 0;
        }

        if (sec_v == "") {
            sec_v = 0;
        }

        var totalSeconds = parseFloat(min_v) * 60 + parseFloat(sec_v);
        //console.log(totalSeconds)
        // 1分ごとの通話料を計算
        var cost = Math.ceil(totalSeconds / 60) * 22;

        ryokin.innerText = cost
        c3022(totalSeconds, cost)
    });

    // 携帯2(08:00~21:59)
    type3.addEventListener("click", function () {
        var min_v = min.value;
        var sec_v = sec.value;

        if (min_v == "") {
            min_v = 0;
        }

        if (sec_v == "") {
            sec_v = 0;
        }

        var totalSeconds = parseFloat(min_v) * 60 + parseFloat(sec_v);
        //console.log(totalSeconds)
        // 1分ごとの通話料を計算
        var cost = Math.ceil(totalSeconds / 60) * 27.5;

        ryokin.innerText = cost
        c3022(totalSeconds, cost)
    });

    type4.addEventListener("click", function () {
        var min_v = min.value;
        var sec_v = sec.value;

        if (min_v == "") {
            min_v = 0;
        }

        if (sec_v == "") {
            sec_v = 0;
        }

        var totalSeconds = parseFloat(min_v) * 60 + parseFloat(sec_v);
        ryokin.innerText = "0";
        c3022(totalSeconds, 0)
    });

    k12.addEventListener('click', function () {
        alert('携帯①: 22:00~翌07:59通話分\n携帯②: 08:00~21:59通話分');
    });
});

// 通常料金で計算(22円/30秒)
function c3022(c_sec, c_rk) {
    // 30秒ごとの通話料を計算
    var cost = Math.ceil(c_sec / 30) * 22;
    var tokutoku = cost - c_rk
    toku.innerText = tokutoku;

    var currentURL = window.location.href;

    // URLに #call を追加
    var newURL = currentURL + '#call';

    // 新しいURLにリダイレクト
    window.location.href = newURL;
}
~~~

~~~js:stopwatch.js
'use strict';

if (!String.prototype.padStart) {
    String.prototype.padStart = function (targetLength, padString) {
        targetLength = targetLength >> 0; // floor if number or convert non-number to 0
        padString = String(typeof padString !== 'undefined' ? padString : ' ');
        if (this.length >= targetLength) {
            return String(this);
        } else {
            targetLength = targetLength - this.length;
            if (targetLength > padString.length) {
                padString += padString.repeat(targetLength / padString.length); // append to original to ensure we are longer than needed
            }
            return padString.slice(0, targetLength) + String(this);
        }
    };
}


document.addEventListener("DOMContentLoaded", function () {
    var time = document.getElementById('time');
    var startButton = document.getElementById('start');
    var stopButton = document.getElementById('stop');
    var resetButton = document.getElementById('reset');
    var transform = document.getElementById('transform');
    var m = "0"
    var s = "0"

    // 開始時間
    let startTime;
    // 停止時間
    let stopTime = 0;
    // タイムアウトID
    let timeoutID;

    // 時間を表示する関数
    function displayTime() {
        const currentTime = new Date(Date.now() - startTime + stopTime);
        m = String(currentTime.getMinutes()).padStart(2, '0');
        s = String(currentTime.getSeconds()).padStart(2, '0');

        time.textContent = `${m}:${s}`;
        timeoutID = setTimeout(displayTime, 10);
    }

    // スタートボタンがクリックされたら時間を進める
    startButton.addEventListener('click', function () {
        startButton.disabled = true;
        stopButton.disabled = false;
        resetButton.disabled = true;
        transform.disabled = true;
        startTime = Date.now();
        displayTime();
    });

    // ストップボタンがクリックされたら時間を止める
    stopButton.addEventListener('click', function () {
        startButton.disabled = false;
        stopButton.disabled = true;
        resetButton.disabled = false;
        transform.disabled = false;
        clearTimeout(timeoutID);
        stopTime += (Date.now() - startTime);
    });

    // リセットボタンがクリックされたら時間を0に戻す
    resetButton.addEventListener('click', function () {
        startButton.disabled = false;
        stopButton.disabled = true;
        resetButton.disabled = true;
        time.textContent = '00:00';
        stopTime = 0;
    });

    transform.addEventListener('click', function () {
        var min = document.getElementById("min");
        var sec = document.getElementById("sec");

        min.value = m
        sec.value = s
    })
})
~~~
今はこれをAndroid4のクソ古い端末でローカル鯖立てて動かしているので最新の機種だとなくても良いコードが多少混ざってます。ストップウォッチはほぼ下のサイトから拝借(パクった)しました。

https://tcd-theme.com/2022/06/javascript-stopwatch.html

# おわりに
Softbank光だとホワイト光電話ってのもあって月額400円ぐらいかかるのですが06などの0AB-J番号が使えます。ぶっちゃけおうち割だから料金は変わらないし携帯宛はホワイトひかり電話のほうが安いのですが番号発行に1100円かかるらしいし様子をみながらかなーと思っています。(変えたらホワイトひかり電話Verも作るかも)