2015js2ts\amazon_record.ts 2016\amazon_record.ts
1// Amazonの注文履歴をTSV形式で出力するスクリプト 1// Amazonの注文履歴をTSV形式で出力するスクリプト
2// 2//
3// 2015-01-01 時点での DOM 構造に対応, GoogleCrome, Opera でテスト済。 3// 2015-01-01 時点での DOM 構造に対応, GoogleCrome, Opera でテスト済。
4// formatEntry関数を書き換えれば自由な書式で出力できます。 4// formatEntry関数を書き換えれば自由な書式で出力できます。
5// 5//
6// 参考: 6// 参考:
7//  - Amazonの注文履歴をCSV形式にして出力するスクリプト 7//  - Amazonの注文履歴をCSV形式にして出力するスクリプト
8//    https://gist.github.com/arcatdmz/8500521 8//    https://gist.github.com/arcatdmz/8500521
9//  - Amazon で使った金額の合計を出す奴 (2014 年バージョン) 9//  - Amazon で使った金額の合計を出す奴 (2014 年バージョン)
10//    https://gist.github.com/polamjag/866a8af775c44b3c1a6d 10//    https://gist.github.com/polamjag/866a8af775c44b3c1a6d
11  11 
12(function () { 12(function () {
13  13 
14    // 各注文履歴をTSVフォーマットにして返す 14    // 各注文履歴をTSVフォーマットにして返す
15    var datePattern = new RegExp("(\\d{4})年(\\d{1,2})月(\\d{1,2})日"); 15    var datePattern = new RegExp("(\\d{4})年(\\d{1,2})月(\\d{1,2})日");
16    function formatEntry(entry) { 16    function formatEntry(entry) {
17        console.log(entry); 17        console.log(entry);
18        entry.date.match(datePattern); 18        entry.date.match(datePattern);
19        var year = RegExp.$1; 19        var year = RegExp.$1;
20        var month = RegExp.$2; if (month.length <= 1) month = "0" + month; 20        var month = RegExp.$2; if (month.length <= 1) month = "0" + month;
21        var day = RegExp.$3; if (day.length <= 1) day = "0" + day; 21        var day = RegExp.$3; if (day.length <= 1) day = "0" + day;
22        var date = "" + year + "/" + month + "/" + day; 22        var date = "" + year + "/" + month + "/" + day;
23        var arr = [date, entry.name, entry.author, entry.url]; 23        var arr = [date, entry.name, entry.price, entry.url];
24        return arr.join('\t') + "\n"; 24        return arr.join('\t') + "\n";
25    } 25    }
26  26 
27    function popup(content) { 27    function popup(content) {
28        var generator = window.open('', 'name', 'height=250,width=700'); 28        var generator = window.open('', 'name', 'height=250,width=700');
29        generator.document.write('<html><head><title>Amazon to TSV</title>'); 29        generator.document.write('<html><head><title>Amazon to TSV</title>');
30        generator.document.write('</head><body>'); 30        generator.document.write('</head><body>');
31        generator.document.write('<pre>'); 31        generator.document.write('<pre>');
32        generator.document.write(content); 32        generator.document.write(content);
33        generator.document.write('</pre>'); 33        generator.document.write('</pre>');
34        generator.document.write('</body></html>'); 34        generator.document.write('</body></html>');
35        generator.document.close(); 35        generator.document.close();
36        return generator; 36        return generator;
37    } 37    }
38  38 
39    var itemDelimiter = " / "; 39    var itemDelimiter = " / ";
40    var total = {}; 40    var total = {};
41    var year = '2014'; 41    var year = 2016;
42    var nyear: number = 2014;   
43    var all = false; 42    var all = false;
44  43 
45    function init(num : number) { 44    function init(num : number) {
46        if (typeof num !== 'number') { 45        if (typeof num !== 'number') {
47            var num = 0; 46            var num = 0;
48            $('<div/>').css({ 47            $('<div/>').css({
49                position: 'fixed', 48                position: 'fixed',
50                left: 0, 49                left: 0,
51                top: 0, 50                top: 0,
52                width: '100%', 51                width: '100%',
53                height: '100%', 52                height: '100%',
54                zIndex: 1000, 53                zIndex: 1000,
55                backgroundColor: 'rgba(0,0,0,.7)', 54                backgroundColor: 'rgba(0,0,0,.7)',
56                color: '#fff', 55                color: '#fff',
57                fontSize: 30, 56                fontSize: 30,
58                textAlign: 'center', 57                textAlign: 'center',
59                paddingTop: '15em' 58                paddingTop: '15em'
60            }).attr('id', '___overlay').text('Amazonいくら使った?').appendTo('body'); 59            }).attr('id', '___overlay').text('Amazonいくら使った?').appendTo('body');
61            year = window.prompt('何年分の注文を集計しますか?\n - 半角数字4桁で入力してください\n - 全期間を集計する場合は「all」と入力します', year); 60            var inp = window.prompt('何年分の注文を集計しますか?\n - 半角数字4桁で入力してください\n - 全期間を集計する場合は「all」と入力します', year.toString());
62            if (year === 'all') { 61            if (inp === 'all') {
63                all = true; 62                all = true;
64                year = jQuery('div.top-controls select option:last').val().match(/[0-9]/g).join(''); 63                year = jQuery('div.top-controls select option:last').val().match(/[0-9]/g).join('');
65            } else if (!/^[0-9]{4}$/.test(year)) { 64            } else if (!/^[0-9]{4}$/.test(inp)) {
66                alert('正しい数値を入力してください'); 65                alert('正しい数値を入力してください');
67                $('#___overlay').remove(); 66                $('#___overlay').remove();
68                return false; 67                return false;
69            } 68            }
70            nyear = Number(year); 69            else { 
  70                year = Number(inp);
  71            } 
71        } 72        }
72        // 第二引数を true にすると各商品とかエラーを逐一表示する 73        // 第二引数を true にすると各商品とかエラーを逐一表示する
73        var progress = load(num, false); 74        var progress = load(num, false);
74        $('#___overlay').text(nyear + '年の集計中…  / ' + (num + 1) + 'ページ目'); 75        $('#___overlay').text(year.toString() + '年の集計中…  / ' + (num + 1) + 'ページ目');
75        progress.done(function (results) { 76        progress.done(function (results) {
76            if (typeof total[nyear] === 'undefined') { 77            if (typeof total[year] === 'undefined') {
77                total[nyear] = results; 78                total[year] = results;
78            } else { 79            } else {
79                total[nyear] = total[nyear].concat(results); 80                total[year] = total[year].concat(results);
80            } 81            }
81            init(num + 1); 82            init(num + 1);
82        }).fail(function () { 83        }).fail(function () {
83            if (all && new Date().getFullYear() > nyear) { 84            if (all && new Date().getFullYear() > year) {
84                nyear++; 85                year++;
85                init(0); 86                init(0);
86            } else { 87            } else {
87                var _total = 0; 88                var _total = 0;
88                var _content = ""; 89                var _content = "";
89                jQuery.each(total, function (nyear, results) { 90                jQuery.each(total, function (year, results) {
90                    var yen = 0; 91                    var yen = 0;
91                    jQuery.each(results, function () { 92                    jQuery.each(results, function () {
92                        yen += this.price; 93                        yen += this.price;
93                        $.each(this.items, function (i, item) { 94                        $.each(this.items, function (i, item) {
94                            _content += formatEntry(item); 95                            _content += formatEntry(item);
95                        }); 96                        });
96                    }); 97                    });
97                    _total += yen; 98                    _total += yen;
98                }); 99                });
99                // result 100                // result
100                $('#___overlay').remove(); 101                $('#___overlay').remove();
101                alert('合計 ' + _total + ' 円'); 102                alert('合計 ' + _total + ' 円');
102                popup(_content); 103                popup(_content);
103                console.log('合計 ' + _total + ' 円'); 104                console.log('合計 ' + _total + ' 円');
104            } 105            }
105        }); 106        });
106    } 107    }
107  108 
108    function load(num: number, verbose: boolean) {  109    function parsePage(data: string, results: any[], lnks: any[], verbose: boolean) {
109        var df = jQuery.Deferred();   
110        var page = get(num, verbose);   
111        page.done(function (data : string) {  
112            var dom = jQuery.parseHTML(data); 110        var dom = jQuery.parseHTML(data);
113            var results = [];  
114  111 
115            jQuery(dom).find('div.order').each(function () { 112        jQuery(dom).find('div.order').each(function () {
116                var box = jQuery(this); 113            var box = jQuery(this);
  114            var emphasis = jQuery(box.find("a.a-link-emphasis"));
  115            if (emphasis.length > 0) {
  116                var path = emphasis.attr('href').trim().replace('/ref=/', '');
  117                var lnk = 'https://www.amazon.co.jp' + path;
  118                lnks.push(lnk);
  119            } else {
117                var dateText = jQuery(box.find('div.order-info span.value')[0]).text().trim(); 120                var dateText = jQuery(box.find('div.order-info span.value')[0]).text().trim();
118   
119                var items = []; 121                var items = [];
120                var item = {}; 122                var item = {};
121                var pubarr = box.find("div.a-row > span.a-size-small"); 123                var pubarr = box.find("div.a-row > span.a-size-small");
122                box.find("div.a-row > a.a-link-normal").each(function (j) { 124                box.find("div.a-row > a.a-link-normal").each(function (j) {
123                    item = {}; 125                    item = {};
124                    item['name'] = $(this).text().trim(); 126                    item['name'] = $(this).text().trim();
125                    item['path'] = $(this).attr('href').trim();  127                    item['path'] = $(this).attr('href').trim().replace(/ref=.*/, ''); 
126                    item['url'] = 'https://www.amazon.co.jp' + item['path']; 128                    item['url'] = 'https://www.amazon.co.jp' + item['path'];
127                    item['date'] = dateText; 129                    item['date'] = dateText;
128                    item['author'] = $(pubarr[j * 2]).text().trim().replace(/(\n)/g, '');  130                    item['author'] = $(pubarr[j * 2]).text().trim().replace(/(\n)/g, '').replace(/ +/g, ' '); 
129                    item['price'] = $(this).parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().find(".order-info").find(".a-span2").find(".a-size-base").find(".value").text().trim(); 131                    item['price'] = $(this).parent().parent().find("span.a-color-price").text().trim();
130                    items.push(item); 132                    items.push(item);
131                }); 133                });
132  134 
133                var priceText = jQuery(box.find('div.order-info span.value')[1]).text(); 135                var priceText = jQuery(box.find('div.order-info span.value')[1]).text();
134                var price = Number(priceText.match(/[0-9]/g).join('')); 136                var price = 0;
  137                if (priceText.match(/[0-9]/g) != null) { 
  138                    price = Number(priceText.match(/[0-9]/g).join('')); 
  139                } 
  140 
  141                if (verbose) 
  142                    console.log(item, price); 
  143                results.push({ 'date': dateText, 'items': items, 'price': price }); 
  144            } 
  145        }); 
  146    } 
  147 
  148    function parsePage2(data: string, results: any[], verbose: boolean) { 
  149        var dom = jQuery.parseHTML(data); 
  150 
  151        var dateText = jQuery(dom).find('span.order-date-invoice-item').first().text().trim(); 
  152        var items = []; 
  153        var item = {}; 
  154        jQuery(dom).find('div.shipment-is-delivered').each(function () { 
  155            var box = jQuery(this); 
  156            var pubarr = box.find("div.a-spacing-base"); 
  157            box.find("div.a-row > a.a-link-normal").each(function (j) { 
  158                item = {}; 
  159                item['name'] = $(this).text().trim(); 
  160                //item['path'] = $(this).attr('href').trim(); 
  161                item['path'] = $(this).attr('href').trim().replace(/ref=.*/, ''); 
  162                item['url'] = 'https://www.amazon.co.jp' + item['path']; 
  163                item['date'] = dateText; 
  164                item['author'] = $(pubarr[j * 2]).text().trim().replace(/(\n)/g, '').replace(/ +/g, ' '); 
  165                item['price'] = $(this).parent().parent().find("span.a-color-price").text().trim(); 
  166                items.push(item); 
  167            }); 
  168        }); 
  169 
  170        var priceText = jQuery(dom).find('div.a-first div.a-fixed-right-grid-inner div.a-span-last span.a-text-bold').text(); 
  171        var price = 0; 
  172        if (priceText.match(/[0-9]/g) != null) { 
  173            price = Number(priceText.match(/[0-9]/g).join('')); 
  174        } 
135  175 
136                if (verbose) console.log(item, price); 176        if (verbose)
  177            console.log(item, price);
137                results.push({ 'date': dateText, 'items': items, 'price': price }); 178        results.push({ 'date': dateText, 'items': items, 'price': price });
  179    }
  180 
  181    function load(num: number, verbose: boolean) {
  182        var df = jQuery.Deferred();
  183        var results = [];
  184        var lnks = [];
  185        var page = get(num, verbose);
  186        page.done(function (data: string) {
  187            parsePage(data, results, lnks, verbose);
  188            var dfunc = function (url) {
  189                var df2 = jQuery.Deferred();
  190                get_url(url, verbose)
  191                    .done(function (data: string) {
  192                        parsePage2(data, results, verbose);
  193                        df2.resolve();
  194                    });
  195                return df2.promise();
  196            };
  197            if (lnks.length > 0) {
  198                var dlist = [];
  199                for (var url of lnks) {
  200                    dlist.push(dfunc(url));
  201                }
  202                $('#___overlay').text(year.toString() + '年の集計中…  / ' + (num + 1) + 'ページ目(サブ' + lnks.length + ')');
  203 
  204                var dwhen = jQuery.when.apply(null, dlist);
  205                dwhen.done(function () {
  206                    if (results.length <= 0) { df.reject(); }
  207                    else { df.resolve(results); }
  208                })
  209                    .fail(function(){
  210                        $('#___overlay').text(year.toString() + '年の集計中…  / ' + (num + 1) + 'ページ目(サブ 失敗)');
  211                        setTimeout(() => {
  212                            if (verbose) console.log("fail");
  213                        }, 500);
138            }); 214                });
139  215 
140            if (results.length <= 0) df.reject(); 216            } else { 
141            else df.resolve(results); 217                if (results.length <= 0) { df.reject(); } 
  218                else { df.resolve(results); } 
  219            } 
142        }); 220        });
  221 
143        return df.promise(); 222        return df.promise();
144    } 223    }
145  224 
146    function get(num: number, verbose: boolean) { 225    function get(num: number, verbose: boolean) {
  226        var url = 'https://www.amazon.co.jp/gp/css/order-history?digitalOrders=1&unifiedOrders=1&orderFilter=year-' + year.toString() + '&startIndex=' + num * 10;
  227        return get_url(url, verbose);
  228    }
  229 
  230    function get_url(strUrl: string, verbose: boolean) {
147        var df = jQuery.Deferred(); 231        var df = jQuery.Deferred();
148        jQuery.ajax({ 232        jQuery.ajax({
149            url: 'https://www.amazon.co.jp/gp/css/order-history?digitalOrders=1&unifiedOrders=1&orderFilter=year-' + year + '&startIndex=' + num * 10,  233            url: strUrl, 
150            beforeSend: function (xhr) { 234            beforeSend: function (xhr) {
151                //xhr.setRequestHeader('X-Requested-With', { toString: function () { return ''; } });  
152                function toString(): string{ return ''; } 235                function toString(): string { return ''; }
153                xhr.setRequestHeader('X-Requested-With', 'toString' ); 236                xhr.setRequestHeader('X-Requested-With', 'toString');
154                //xhr.setRequestHeader('X-Requested-With', '');//NG  
155            }, 237            },
156            success: function (data) { 238            success: function (data) {
157                df.resolve(data); 239                df.resolve(data);
158            } 240            }
159        }) 241        })
160            .fail(function (jqXHR, msg) { 242            .fail(function (jqXHR, msg) {
161                if (verbose) console.log("fail", msg); 243                if (verbose) console.log("fail", msg);
162            }); 244            });
163        return df.promise(); 245        return df.promise();
164    } 246    }
165  247 
166    if (typeof jQuery !== 'function') { 248    if (typeof jQuery !== 'function') {
167        var d = document; 249        var d = document;
168        var s = d.createElement('script'); 250        var s = d.createElement('script');
169        s.src = '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'; 251        s.src = '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js';
170        s.onload = e => { init(null); } 252        s.onload = e => { init(null); }
171        d.body.appendChild(s); 253        d.body.appendChild(s);
172    } else { 254    } else {
173        init(null); 255        init(null);
174    } 256    }
175})(); 257})();