LoginSignup
14
19

便利ページ:Javascriptでちょっとした便利な機能を作ってみた

Last updated at Posted at 2019-04-19

ちょっとしたことをプログラミングすればすぐできることなんだけど、そのために実装するのが面倒だったり、それがしょっちゅう必要な場面に遭遇することがあります。また、バッチファイルを用意するにしても、その置き場所を忘れたり、管理が面倒だったりします。
そこで、Javascriptで実装して、いつでもブラウザを開けば使えるようにします。

以下のような機能を組み込みます。一部の機能は、サーバと連携するため、RESTfulなサーバの構築が必要なものがあります。

・QRコード
  QRコードを生成します。
・エンコード
  Base64エンコード/デコード、URLエンコード/デコード、HTMLエンティティのエンコードをします。
・パスワード
  乱数を使ってパスワードを生成します。
・バイト配列
  16進数文字列をみやすくしたり、C言語形式、Java形式にします。
・日時
  日時をUnixタイムに変換したり、1時間後などのDurationを演算できます。
・基数
  2進数から16進数まで、基数の変換をします。
・クリップ
  ちょっとした文字列を記憶します。
・整形
  JSON、Javascript、HTML、CSSのコードを見やすい形に整形します。
・スクレイピング
  Webページをスクレイピングします。ただし、サーバの準備が必要です。
・通知
  LINE通知やGmail送信をします。ただし、サーバの準備が必要です。

Gitにも上げておきました。
 https://github.com/poruruba/utilities

参考までに、以下にデモとしてアクセスできるようにしておきました。
 https://poruruba.github.io/utilities/

全体で利用しているJavascript外部モジュールは以下の通りです。

・Bootstrap(v3.4.1)
 https://getbootstrap.com/docs/3.4/
・Vue
 https://jp.vuejs.org/index.html
・js-cookie
 https://github.com/js-cookie/js-cookie
・clipboard.js
 https://github.com/zenorocha/clipboard.js

(2019/4/20:追記)
HTMLエンティティのエンコードを追加しました。
(2019/5/12)
日時のページが使いにくかったので改良しました。

(続編)
 便利ページ:自分のQiita記事を一覧表示
 便利ページ:元号を変換してみた
 便利ページ:トレンドキーワードを取得してみた
 便利ページ:Javascriptで暗号化など追加
 便利ページ:Javascriptでバイナリファイル操作
 便利ページ:Javascriptでカラーピッカー
 便利ページ:JavascriptでQRコードスキャン
 便利ページ:データURLを生成する
 便利ページ:Javascriptでアイコンファイルを生成する
 便利ページ:Web Bluetooth APIでBLEデバイスに接続
 便利ページ:WebSocketをサクッと触れるページを作ってみた
 便利ページ:JavascriptでHTML5をサクッと試す
 便利ページ:javascriptで画像をOCRする
 便利ページ:JavascriptでYAMLからJSONに変換
 便利ページ:OpenID Connectのログインページを追加
 便利ページ:ASN.1をデコード
 便利ページ:COMポート出力のコンソールを作る
 便利ページ:Leafletを使って緯度経度の表示や距離を表示する
 便利ページ:plus codeのエンコード・デコード

QRコード

以下のJavascript外部モジュールを使っています。

・qrcode.js
https://davidshimjs.github.io/qrcodejs/

※qrcode.min.jsだと長い文字列でエラーになるようで、minがつかないqrcode.jsを使うようにしました。

ソースコード抜粋です。

start.js
            $('#qrcode_area').empty();
            new QRCode($("#qrcode_area")[0], this.qrcode_input);

エンコード

Base64もURLエンコードも、いずれもJavascriptに標準機能としてあるため、それを使っています。

start.js
        base64_encode: function(encode){
            try{
                if( encode )
                    this.base64_output = btoa(this.base64_input);
                else
                    this.base64_output = atob(this.base64_input);
            }catch( error ){
                alert(error);
            }
        },
        url_encode: function(encode){
            try{
                if( encode )
                    this.url_output = encodeURIComponent(this.url_input);
                else
                    this.url_output = decodeURIComponent(this.url_input);
            }catch( error ){
                alert(error);
            }
        },
        html_encode: function(space){
            const html_entities = {
                '\"': '"',
                '&': '&',
                '\'': ''',
                '<': '&lt;',
                '>': '&gt;',
                ' ': space ? '&nbsp;' : undefined,
            };

            this.html_output = this.html_input.split('').map((entity) => {
                return html_entities[entity] || entity;
            }).join('');
        },

パスワード

ランダムなパスワードを生成したい時がよくありますよね。さらに、数字を入れろだの、記号を入れろだの、いろいろ注文を付けてくることがよくあります。
ですので、そういった特異な文字を含めるかどうかを指定できるようにしました。記号については、サンプルでいくつかの種類を選んでおきました。

start.js
        passwd_create: function(){
            var passwd_num = Number(this.passwd_num);
            var passwd_number_num = this.passwd_check_number ? Number(this.passwd_number_num) : 0;
            var passwd_symbol_num = this.passwd_check_symbol ? Number(this.passwd_symbol_num) : 0;
            if( passwd_num < 1 || passwd_num < (passwd_number_num + passwd_symbol_num) ){
                alert('入力が不正です。');
                return;
            }
            if( (!this.passwd_check_lower_letter && !this.passwd_check_upper_letter) && passwd_num != (passwd_number_num + passwd_symbol_num)){
                alert('入力が不正です。');
                return;
            }

            var kind = Array(passwd_num);
            kind.fill(0);
            for( var i = 0 ; i < passwd_number_num ; i++ )
                kind[i] = 'n';
            for( var i = 0 ; i < passwd_symbol_num ; i++ )
                kind[passwd_number_num + i] = 's';

            for( var i = 0 ; i < passwd_num ; i++ ){
                var index = make_random(passwd_num - 1);
                if( index == i || kind[i] == kind[index] )
                    continue;
                var temp = kind[i];
                kind[i] = kind[index];
                kind[index] = temp;
            }

            const number_pattern = '0123456789';
            var alpha_pattern = '';
            if( this.passwd_check_lower_letter ){
                if( this.passwd_check_ecept_lO )
                    alpha_pattern += "abcdefghijkmnopqrstuvwxyz";
                else
                    alpha_pattern += "abcdefghijklmnopqrstuvwxyz";
            }
            if( this.passwd_check_upper_letter ){
                if( this.passwd_check_ecept_lO )
                    alpha_pattern += "ABCDEFGHJKLMNPQRSTUVWXYZ";
                else
                    alpha_pattern += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            }

            var passwd = '';
            for( var i = 0 ; i < kind.length ; i++ ){
                if( kind[i] == 'n' ){
                    var index = make_random(number_pattern.length - 1);
                    passwd += number_pattern.charAt(index);
                }else if( kind[i] == 's' ){
                    var pattern = this.passwd_symbol_pattern;
                    var index = make_random(pattern.length - 1);
                    passwd += pattern.charAt(index);
                }else{
                    var index = make_random(alpha_pattern.length - 1);
                    passwd += alpha_pattern.charAt(index);
                }
            }

            this.passwd_password = passwd;
        },

バイト配列

プログラミングしていると、16進数の文字列を扱うことが多いのですが、スペースを入れて見やすくしたり、C言語やJava言語の初期化子に指定したくなったりして整形が必要なケースがあります。
ソースコードはちょっと長いので割愛。

日時

日時をUnixタイムで扱いたいことがよくあります。それから、1日後の時間が欲しくてmoment.jsを使いたい時があります。

当然ながら、以下の有名なJavascript外部モジュールを使っています。

・Moment.js(v.2.10.3)
 https://momentjs.com/

start.js
        /* 日時 */
        date_get_now: function(target){
            if( target == 'base')
                this.date_moment = moment();
            else
                this.date_moment_after = moment();
        },
        date_input_dialog: function(target){
            this.date_input_mode = target;
            var temp;
            if( target == 'base')
                temp = moment(this.date_moment);
            else
                temp = moment(this.date_moment_after);
            this.date_input_date = temp.format('YYYY-MM-DD');
            this.date_input_time = temp.format('HH:mm:ss');
            this.dialog_open('#date_input_dialog');
        },
        date_process: function(){
            var temp = moment(this.date_moment);
            if( this.date_duration_unit == 'year' )
                temp.add(Number(this.date_duration), 'years');
            else if( this.date_duration_unit == 'month' )
                temp.add(Number(this.date_duration), 'months');
            else if( this.date_duration_unit == 'day' )
                temp.add(Number(this.date_duration), 'days');
            else if( this.date_duration_unit == 'hour' )
                temp.add(Number(this.date_duration), 'hours');
            else if( this.date_duration_unit == 'minute' )
                temp.add(Number(this.date_duration), 'minutes');
            else if( this.date_duration_unit == 'second' )
                temp.add(Number(this.date_duration), 'seconds');
            this.date_moment_after = temp;
        },
        date_input_process: function(target){
            var date;
            if( target == 'free'){
                if( !this.date_input_free ){
                    alert('入力値が不正です。');
                    return;
                }
                date = this.date_input_free;
            }else{
                if( !this.date_input_date || !this.date_input_time ){
                    alert('入力値が不正です。');
                    return;
                }
                date = this.date_input_date + ' ' + this.date_input_time;
            }

            if( this.date_input_mode == 'base'){
                if( !isNaN(date) )
                    this.date_moment = moment(Number(date));
                else
                    this.date_moment = moment(date);
            }else{
                if( !isNaN(date) )
                    this.date_moment_after = moment(Number(date));
                else
                    this.date_moment_after = moment(date);
            }
            this.dialog_close('#date_input_dialog');
        },
        date_elapsed_process: function(){
            var base = moment(this.date_moment);
            var after = moment(this.date_moment_after);
            if( this.date_duration_unit == 'year' )
                this.date_duration = after.diff(base, 'years');
            else if(this.date_duration_unit == 'month')
                this.date_duration = after.diff(base, 'months');
            else if(this.date_duration_unit == 'day')
                this.date_duration = after.diff(base, 'days');
            else if(this.date_duration_unit == 'hour')
                this.date_duration = after.diff(base, 'hours');
            else if(this.date_duration_unit == 'minute')
                this.date_duration = after.diff(base, 'minutes');
            else if(this.date_duration_unit == 'second')
                this.date_duration = after.diff(base, 'seconds');
        },

基数

10進数、2進数、8進数、16進数を相互に変換します。
また、決まった桁数だけ0埋めする機能も付けました。

start.js
        cardinal_convart: function(radix){
            var base = 0;
            if( radix == 10){
                base = parseInt(this.cardinal_decimal, 10);
            }else if( radix == 2){
                base = parseInt(this.cardinal_binary, 2);
            }else if( radix == 16){
                base = parseInt(this.cardinal_hexadecimal, 16);
            }
            this.base_decimal = Math.floor(base);

            this.cardinal_update();
        },
        cardinal_shift(radix, shift){
            if( shift == 'right' )
                this.base_decimal = Math.floor(this.base_decimal / radix);
            else
                this.base_decimal = Math.floor(this.base_decimal * radix);

            this.cardinal_update();
        },
        cardinal_update: function(){
            this.cardinal_decimal = this.base_decimal;

            var temp = this.base_decimal.toString(2);
            if( this.cardinal_check_binary ){
                for( var i = temp.length ; i < this.cardinal_binary_num; i++ )
                    temp = '0' + temp;
            }
            this.cardinal_binary = temp;
            var temp = this.base_decimal.toString(16);
            if( this.cardinal_check_hexadecimal ){
                for( var i = temp.length ; i < this.cardinal_hexadecimal_num; i++ )
                    temp = '0' + temp;
            }
            this.cardinal_hexadecimal = temp;
        },

整形

JSON、Javascript、HTML、CSS を見やすく整形します。
以下のJavascript外部モジュールを使っています。JSONはJavascriptの標準機能を使っています。

・js-beautify(v1.9.1)
https://github.com/beautify-web/js-beautify

start.js
        arrange_process: function(type){
            try{
                if( type == 'json' )
                    this.json_inout = JSON.stringify(JSON.parse(this.json_inout), null, '\t');
                else if( type == 'javascript')
                    this.js_inout = js_beautify(this.js_inout, { indent_size: 2, space_in_empty_paren: true });
                else if( type == 'css')
                    this.css_inout = css_beautify(this.css_inout, { indent_size: 2, space_in_empty_paren: true });
                else if( type == 'html')
                   this.html_inout = html_beautify(this.html_inout, { indent_size: 2, space_in_empty_paren: true });
            }catch( error ){
                alert(error);
            }
        },

スクレイピング

よく見るWebページを毎回URLを入力して見に行くのが面倒だったので、ボタン一つでWebページから必要な情報だけを取り出します。
ただし、実際の処理はサーバ側で行うようにしていますので、RESTfulサーバの立ち上げが必要です。

今回は、京急線の運行情報をスクレーピングします。
といっても、以下の再掲です。

 京急線の運行情報をLINE Notifyする

クライアントJavascript側は、単にサーバにPOSTしているだけですので、ソースコードは割愛。
サーバ側の方で、アクセス者を限定するため、ApiKeyを指定するようにしています。

通知

任意のメールアドレスまたはLINEアカウントに、メッセージを送信します。
RESTfulサーバの立ち上げが必要です。
メールについては、Gmailアカウントが必要です。
また、LINE通知先はサーバ側で指定するのですが、とりあえず1か所のみにしています。自分に送るぐらいかなあと思いまして。

クライアント側はサーバにPOSTしているだけなので、ソースコードは割愛。

サーバ側

通知とスクレイピング用にRESTfulサーバを立ち上げます。
ぜひ以下も参考にしてください。

SwaggerでRESTful環境を構築する
SwaggerでLambdaのデバッグ環境を作る(1)

サーバ側はこんな感じです。

利用モジュール
・cheerio
・node-fetch
・url
・nodemailer

index.js
const cheerio = require('cheerio');
const fetch = require('node-fetch');
const { URLSearchParams } = require('url');
const nodemailer = require('nodemailer');

const Response = require('../../helpers/response');
const LINE_PERSONAL_ACCESS_TOKEN = process.env.LINE_PERSONAL_ACCESS_TOKEN || LINEパーソナルアクセストークン;
const GMAIL_USER = process.env.GMAIL_USER || Gmailログインメールアドレス;
const GMAIL_PASSWORD = process.env.GMAIL_PASSWORD || Gmailログインパスワード;
const GMAIL_FROM_ADDRESS = process.env.GMAIL_FROM_ADDRESS || Gmail送信元メールアドレス;

const SERVER_APIKEY = ApiKey;

exports.handler = async (event, context, callback) => {
    if( event.path == '/notify-line'){
        var body = JSON.parse(event.body);
        if( body.apikey != SERVER_APIKEY )
            throw 'apikey mismatch';

        return line_notify(body.message, LINE_PERSONAL_ACCESS_TOKEN)
        .then(json =>{
            return new Response({ result: 'OK'});
        });
    }else
    if( event.path == '/notify-gmail' ){
        var body = JSON.parse(event.body);
        if( body.apikey != SERVER_APIKEY )
            throw 'apikey mismatch';

        var message = {
            from : GMAIL_FROM_ADDRESS,
            to : body.mail_address,
            subject : 'Message form Utilities.Notify',
            text : body.message
        };
        var smtpConfig = {
            host: 'smtp.gmail.com',
            port: 465,
            secure: true,
            auth: {
                user : GMAIL_USER,
                pass : GMAIL_PASSWORD
            }
        };
        var transporter = nodemailer.createTransport(smtpConfig);
        return new Promise((resolve, reject) =>{
            transporter.sendMail(message, (err, response) => {
                if( err )
                    return reject(err);                
                return resolve(new Response({ result : 'OK' } ));
            });
        });
    }else
    if( event.path == '/scraping-keikyu'){
        var body = JSON.parse(event.body);
        if( body.apikey != SERVER_APIKEY )
            throw 'apikey mismatch';

        return scraping_keikyu()
        .then( message =>{
            return new Response( { result: 'OK', message : message });
        });
    }
};

function line_notify(message, token){
    var body = {
        message: message
    };
    return do_post_token('https://notify-api.line.me/api/notify', body, token);
}

function do_post_token(url, body, token){
    var data = new URLSearchParams();
    for( var name in body )
        data.append(name, body[name]);

    return fetch(url, {
        method : 'POST',
        body : data,
        headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization' : 'Bearer ' + token }
    })
    .then((response) => {
        if( response.status != 200 )
            throw 'status is not 200';
        return response.json();
    });
}

function do_get(url, qs){
    var params = new URLSearchParams();
    for( var key in qs )
        params.set(key, qs[key] );

    var p = params.toString();
    if( p )
        url += '?' + p;
    return fetch(url, {
        method : 'GET',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    })
    .then((response) => {
        if( response.status != 200 )
            throw 'status is not 200';
        return response.text();
    });
}

function scraping_keikyu(){
    return do_get('http://unkou.keikyu.co.jp/?from=mail')
    .then(text =>{
        const $ = cheerio.load(text);
        return $(".unko-panel").text().trim();
    });
}

以下の部分は環境に合わせて指定してください。

【LINEパーソナルアクセストークン】
 LINE通知を行いたい相手のパーソナルアクセストークンです。LINE Notifyを使っています。まあ、自分ので。詳細は、他のいろいろな方の紹介ページを参考にしていただくか、以下を参考にしてください。

 京急線の運行情報をLINE Notifyする

【Gmailログインメールアドレス】
【Gmailログインパスワード】
 これらは、Gmail通知で使うアカウント情報です。

【Gmail送信元メールアドレス】
 Gmailで送信するときの送信元メールアドレスです。

【ApiKey】
 念のため、サーバが変な攻撃に使われてしまわないように、任意のApiKeyを指定しておきます。ブラウザから通知・スクレイピングするときに同じApiKeyを指定するようにします。

最後に

生活している中で、ちょっとした便利なツールがあるといいなあと思ったら、また追加していこうと思います。

以上

14
19
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
14
19