├── chrome ├── icon-16.png ├── icon-48.png ├── icon-128.png ├── manifest.json ├── kiseppe.css ├── options.js ├── options.html └── content-script.js ├── firefox ├── icon-128.png ├── icon-16.png ├── icon-48.png ├── manifest.json ├── kiseppe.css ├── options.js ├── options.html └── content-script.js ├── images ├── kiseppe-v2-1280x800.jpg ├── kiseppe-chex-1280x800.jpg ├── kiseppe-graph-1280x800.jpg ├── kiseppe-marquee-1400x560.jpg ├── kiseppe-promotiontile-440x280.jpg └── kiseppe-promotiontile-920x680.jpg ├── LICENSE ├── README.md ├── CHANGES.md └── ChangeLog /chrome/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/chrome/icon-16.png -------------------------------------------------------------------------------- /chrome/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/chrome/icon-48.png -------------------------------------------------------------------------------- /chrome/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/chrome/icon-128.png -------------------------------------------------------------------------------- /firefox/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/firefox/icon-128.png -------------------------------------------------------------------------------- /firefox/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/firefox/icon-16.png -------------------------------------------------------------------------------- /firefox/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/firefox/icon-48.png -------------------------------------------------------------------------------- /images/kiseppe-v2-1280x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/images/kiseppe-v2-1280x800.jpg -------------------------------------------------------------------------------- /images/kiseppe-chex-1280x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/images/kiseppe-chex-1280x800.jpg -------------------------------------------------------------------------------- /images/kiseppe-graph-1280x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/images/kiseppe-graph-1280x800.jpg -------------------------------------------------------------------------------- /images/kiseppe-marquee-1400x560.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/images/kiseppe-marquee-1400x560.jpg -------------------------------------------------------------------------------- /images/kiseppe-promotiontile-440x280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/images/kiseppe-promotiontile-440x280.jpg -------------------------------------------------------------------------------- /images/kiseppe-promotiontile-920x680.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yto/kiseppe/HEAD/images/kiseppe-promotiontile-920x680.jpg -------------------------------------------------------------------------------- /chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kiseppe - Price Chart for Amazon Japan Kindle", 3 | "description": "Displays price trends, discounts, and summary links for sales on Amazon Japan's Kindle pages. Only operates on www.amazon.co.jp.", 4 | "version": "2.0.11", 5 | "homepage_url": "https://yapi.ta2o.net/kndlsl/kiseppe/", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "icon-16.png", 9 | "48": "icon-48.png", 10 | "128": "icon-128.png" 11 | }, 12 | "permissions": [ 13 | "storage" 14 | ], 15 | "action": { 16 | "default_popup": "options.html" 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": [ 21 | "https://www.amazon.co.jp/*" 22 | ], 23 | "js": [ 24 | "content-script.js" 25 | ], 26 | "css": [ 27 | "kiseppe.css" 28 | ] 29 | } 30 | ], 31 | "options_ui": { 32 | "page": "options.html", 33 | "open_in_tab": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tatsuo Yamashita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kiseppe - Price Chart for Amazon Japan Kindle", 3 | "description": "Displays price trends, discounts, and summary links for sales on Amazon Japan's Kindle pages. Only operates on www.amazon.co.jp.", 4 | "version": "2.0.11", 5 | "browser_specific_settings": { "gecko": { "id": "kiseppe_ff@example.com" } }, 6 | "homepage_url": "https://yapi.ta2o.net/kndlsl/kiseppe/", 7 | "manifest_version": 3, 8 | "icons": { 9 | "16": "icon-16.png", 10 | "48": "icon-48.png", 11 | "128": "icon-128.png" 12 | }, 13 | "permissions": [ 14 | "storage" 15 | ], 16 | "action": { 17 | "default_popup": "options.html" 18 | }, 19 | "content_scripts": [ 20 | { 21 | "matches": [ 22 | "https://www.amazon.co.jp/*" 23 | ], 24 | "js": [ 25 | "content-script.js" 26 | ], 27 | "css": [ 28 | "kiseppe.css" 29 | ] 30 | } 31 | ], 32 | "options_ui": { 33 | "page": "options.html", 34 | "open_in_tab": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # キセッペ - Kiseppe 2 | 3 | Browser (Chrome/Firefox) Extension : Price Hitory Graph for Japanese Amazon Kindle Books 4 | 5 | 6 | 7 | ## 概要 8 | 9 | キセッペ (Kiseppe) は、 10 | Kindle セール情報サイト「キンセリ」のブラウザ拡張機能です。 11 | 12 | 日本のアマゾン (amazon.co.jp) の Kindle 書籍ページに価格推移グラフを直接表示します。 13 | セール対象作品の場合はセールまとめページへのリンクも表示します。 14 | また、Kindle 本の検索結果ページ、著者ページ、ランキングページなどに割引情報を表示します。 15 | 16 | Kiseppe directly displays a price trend chart on Kindle book pages of Amazon Japan (amazon.co.jp). 17 | If the book is on sale, a link to the sale summary is provided. 18 | Additionally, it displays discount information on Kindle search results, author pages, ranking pages, and similar pages. 19 | 20 | 詳しくは[キセッペの公式ページ](https://yapi.ta2o.net/kndlsl/kiseppe/)をご覧ください。 21 | 22 | ## リンク 23 | 24 | - [Kiseppe - Price Chart for Amazon Japan Kindle - Chrome ウェブストア](https://chrome.google.com/webstore/detail/kiseppe-price-chart-for-a/jhmbgbjpbiiklgmfabbcldoddlljplle) 25 | - Chrome 拡張機能のブラウザ公式の入手先 26 | - [Kiseppe - Kindle Price Chart – 🦊 Firefox (ja) 向け拡張機能を入手](https://addons.mozilla.org/ja/firefox/addon/kiseppe-price-chart-kindle/) 27 | - Firefox 拡張機能のブラウザ公式の入手先 28 | - [キセッペ (キンセリのブラウザ拡張機能)](https://yapi.ta2o.net/kndlsl/kiseppe/) 29 | - キセッペの公式ページです 30 | - [キンセリ](https://yapi.ta2o.net/kndlsl) 31 | - Kindle セール情報サイト 32 | - キセッペではキンセリで整備しているデータを利用しています 33 | 34 | ## 作者 35 | 36 | [yto](https://x.com/yto) 37 | 38 | キンセリの開発・運営を行っています 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | ## v2.0.10 / 2025-01-24 4 | - feature: カルーセルにバッジ・背景色を表示 5 | - feature: storefront ページのカルーセルに対応 6 | - fix: ASINページ(個別作品ページ)でバッジが出なくなっていたのを修正 7 | - fix: 著者ページ(Author Page)のレイアウト変更に対応 8 | 9 | ## v2.0.9 / 2025-01-14 10 | - feature: Wishlist のグリッド表示でバッジ・ボタン・背景色を表示 11 | - fix: Wishlist で割引率が表示されなくなった問題を修正 (by Foo-x) 12 | 13 | ## v2.0.8 / 2024-12-24 14 | - fix: Octopus 表示で強調表示が機能しなくなっていたので修正 15 | - fix: Wishlist でグリッドからリストに切り替えると強調表示されないので修正 16 | - fix: 検索結果ページでkindleunlimited作品の価格が0円と認識されてしまうのを修正 17 | 18 | ## v2.0.7 / 2024-07-02 19 | - fix: アマゾンの検索結果ページでバッジがでなくなることがあったのを修正 20 | 21 | ## v2.0.6 / 2024-06-21 22 | - fix: シリーズページで機能しなくなっていたのを修正 23 | 24 | ## v2.0.5 / 2024-01-27 25 | - fix: 価格推移グラフ表示ボタンの z-index の調整 26 | 27 | ## v2.0.4 / 2023-12-28 28 | - fix: バグ修正と Amazon サイトレイアウト変更対応 29 | 30 | ## v2.0.3 / 2023-11-24 31 | - fix: ユーザから報告されたバグ (処理が止まるケース) を修正 32 | 33 | ## v2.0.2 / 2023-11-23 34 | - fix: 主に価格・ポイント情報抽出部分のバグ修正 35 | - feature: キセッペご意見箱へのリンク (オプション設定画面に) 36 | 37 | ## v2.0.1 / 2023-11-06 38 | - fix: innerHTML で変数を使っている箇所の脆弱性対応 (APIサーバが乗っ取られると危険なため) 39 | - fix: Kindle ページか否かの判定ロジックの修正 (グラフが挿入されないケースがあった) 40 | - feature: ほしい物リスト (wishlist) ページにも対応 41 | - 追記: 審査通って Chrome ウェブストア、Firefox ADD-ONS で公開 42 | 43 | ## v2.0.0 / 2023-11-01 44 | - Chrome, Firefox の審査に通った版 (rc と同じ) 45 | 46 | ## v2.0.0-rc / 2023-10-30 47 | - Chrome, Firefox の審査に出した版 48 | 49 | ## v2.0.0-beta.2 / 2023-10-29 50 | - downgrade: まだ見ぬケースで暴走する可能性を危惧し、カルーセルへの処理をオプションとした 51 | - update: セール判定精度向上のため、検索結果ページなどでも API に価格・ポイント還元情報を追加するように変更 52 | - fix: ChatGPT の添削を受けていろいろ修正(例外処理、エラー対策などを中心に) 53 | 54 | ## v2.0.0-beta / 2023-10-25 55 | - feature: popup.html, options.html に運営側からのお知らせを表示する仕組み 56 | - update: API の URL を本番用 (v2 用) のに変更 57 | - delete: デバグ用の仕組みを削除(役目を終えたため) 58 | 59 | ## v2.0.0-alpha.3 / 2023-10-21 60 | - feature: options を popup でも使うようにした 61 | - fix: Kindle Unlimited が0円価格と認識されてしまい実質100%オフと表示されるバグ 62 | 63 | ## v2.0.0-alpha.2 / 2023-10-19 64 | - update: firefox のコードを chrome からコピーして使えるようにした 65 | 66 | ## v2.0.0-alpha / 2023-10-15 67 | - feature: Kindle 本の検索結果ページ、著者ページ、ランキングページなどに実質割引率バッヂが表示される 68 | - feature: セール中の作品の背景色がピンクっぽくなる(濃度は実質割引率と連動) 69 | - feature: 価格推移グラフを表示するためのアイコンが表示される 70 | - feature: シリーズ中にセール作品があればシリーズ作品リンクにアイコンが表示される 71 | 72 | ## v1.0.2 / 2023-04-02 73 | - memo: Firefox の審査通った後のソース (リリース忘れ) 74 | 75 | ## v1.0.1 / 2023-03-16 76 | - memo: Chrome ウェブストア審査通過後のソース 77 | 78 | ## v1.0.0 / 2023-03-13 79 | - feature: Kindle 本の個別作品ページに価格推移グラフが表示される 80 | 81 | -------------------------------------------------------------------------------- /chrome/kiseppe.css: -------------------------------------------------------------------------------- 1 | /* 2 | animation 3 | */ 4 | @keyframes zoomin_bottom_left { 5 | 0% { font-size: 2em; color: transparent; bottom: 0.5em; } 6 | 100% { } 7 | } 8 | @keyframes araware { 9 | 0% { color: transparent; background-color: transparent; } 10 | 100% { } 11 | } 12 | 13 | /* 14 | price graph iframe 15 | */ 16 | #kiseppe { 17 | width: 100%; 18 | height: 0px; 19 | border: 0; 20 | overflow: hidden; 21 | } 22 | 23 | /* 24 | badges and buttons 25 | */ 26 | .kiseppe-jsdr-badge { 27 | position: absolute; 28 | padding: 2px 4px; 29 | line-height: 1.2em; 30 | text-align: center; 31 | font-size: x-small; 32 | background-color: rgba(255,0,0,0.9); /* brown */ 33 | color: white; 34 | animation: araware 1.0s; 35 | } 36 | .kiseppe-series-sale-badge { 37 | color: red; /* only for Windows Chrome */ 38 | animation-name: araware; 39 | animation-duration: 0.7s; 40 | animation-iteration-count: 3; 41 | } 42 | .kiseppe-pg-btn { 43 | position: absolute; 44 | bottom: 0; 45 | left: 0; 46 | z-index: 100; 47 | cursor: pointer; 48 | font-size: 1em; 49 | color: rgba(0,0,0,0.9); 50 | /* animation: zoomin_bottom_left 1.5s; */ 51 | } 52 | 53 | /* 54 | price graph popup 55 | */ 56 | dialog#popup_modal { 57 | border: none; 58 | border-radius: 8px; 59 | position: fixed; 60 | top: 50%; 61 | left: 50%; 62 | transform: translate(-50%, -50%); 63 | z-index: 9999; 64 | max-height: 90vh; /* 画面の90%の高さまで */ 65 | overflow-y: auto; /* 高さを超えた場合にスクロール */ 66 | min-width: 300px; 67 | } 68 | dialog#popup_modal::backdrop { 69 | background: rgba(0,0,0,.5); 70 | } 71 | #popup_modal #pg_container { 72 | width: 850px; 73 | max-width: 100%; 74 | min-width: 90%; 75 | text-align: center; 76 | } 77 | #popup_modal #pg_container .pg_item_info { 78 | max-height: 100%; 79 | } 80 | #popup_modal #pg_container .pg_item_title { 81 | font-weight: bold; 82 | margin-bottom: 0.5rem; 83 | text-overflow: ellipsis; 84 | overflow: hidden; 85 | white-space: nowrap; 86 | } 87 | #popup_modal #pg_container .pg_button_close { 88 | background: #f44336; 89 | color: white; 90 | border: none; 91 | padding: 10px 20px; 92 | fontSize: 16px; 93 | border-radius: 10px; 94 | cursor: pointer; 95 | position: absolute; 96 | top: 7px; 97 | right: 7px; 98 | } 99 | #popup_modal #pg_container iframe { 100 | width: 100%; 101 | height: 100%; 102 | border: 0; 103 | overflow: visible; 104 | } 105 | -------------------------------------------------------------------------------- /firefox/kiseppe.css: -------------------------------------------------------------------------------- 1 | /* 2 | animation 3 | */ 4 | @keyframes zoomin_bottom_left { 5 | 0% { font-size: 2em; color: transparent; bottom: 0.5em; } 6 | 100% { } 7 | } 8 | @keyframes araware { 9 | 0% { color: transparent; background-color: transparent; } 10 | 100% { } 11 | } 12 | 13 | /* 14 | price graph iframe 15 | */ 16 | #kiseppe { 17 | width: 100%; 18 | height: 0px; 19 | border: 0; 20 | overflow: hidden; 21 | } 22 | 23 | /* 24 | badges and buttons 25 | */ 26 | .kiseppe-jsdr-badge { 27 | position: absolute; 28 | padding: 2px 4px; 29 | line-height: 1.2em; 30 | text-align: center; 31 | font-size: x-small; 32 | background-color: rgba(255,0,0,0.9); /* brown */ 33 | color: white; 34 | animation: araware 1.0s; 35 | } 36 | .kiseppe-series-sale-badge { 37 | color: red; /* only for Windows Chrome */ 38 | animation-name: araware; 39 | animation-duration: 0.7s; 40 | animation-iteration-count: 3; 41 | } 42 | .kiseppe-pg-btn { 43 | position: absolute; 44 | bottom: 0; 45 | left: 0; 46 | z-index: 100; 47 | cursor: pointer; 48 | font-size: 1em; 49 | color: rgba(0,0,0,0.9); 50 | /* animation: zoomin_bottom_left 1.5s; */ 51 | } 52 | 53 | /* 54 | price graph popup 55 | */ 56 | dialog#popup_modal { 57 | border: none; 58 | border-radius: 8px; 59 | position: fixed; 60 | top: 50%; 61 | left: 50%; 62 | transform: translate(-50%, -50%); 63 | z-index: 9999; 64 | max-height: 90vh; /* 画面の90%の高さまで */ 65 | overflow-y: auto; /* 高さを超えた場合にスクロール */ 66 | min-width: 300px; 67 | } 68 | dialog#popup_modal::backdrop { 69 | background: rgba(0,0,0,.5); 70 | } 71 | #popup_modal #pg_container { 72 | width: 850px; 73 | max-width: 100%; 74 | min-width: 90%; 75 | text-align: center; 76 | } 77 | #popup_modal #pg_container .pg_item_info { 78 | max-height: 100%; 79 | } 80 | #popup_modal #pg_container .pg_item_title { 81 | font-weight: bold; 82 | margin-bottom: 0.5rem; 83 | text-overflow: ellipsis; 84 | overflow: hidden; 85 | white-space: nowrap; 86 | } 87 | #popup_modal #pg_container .pg_button_close { 88 | background: #f44336; 89 | color: white; 90 | border: none; 91 | padding: 10px 20px; 92 | fontSize: 16px; 93 | border-radius: 10px; 94 | cursor: pointer; 95 | position: absolute; 96 | top: 7px; 97 | right: 7px; 98 | } 99 | #popup_modal #pg_container iframe { 100 | width: 100%; 101 | height: 100%; 102 | border: 0; 103 | overflow: visible; 104 | } 105 | -------------------------------------------------------------------------------- /chrome/options.js: -------------------------------------------------------------------------------- 1 | const NOTICE_API = 'https://www.listasin.net/api/notice.cgi'; 2 | 3 | const save_options = async () => { 4 | try { 5 | const promises = [...document.querySelectorAll('#option-setting input')].map(e => { 6 | const value = (e.type == 'checkbox') ? e.checked : e.value; 7 | return chrome.storage.local.set({[e.id]: value}); 8 | }); 9 | await Promise.all(promises); 10 | console.log("Options saved successfully"); 11 | } catch (error) { 12 | console.error("Failed to save options:", error); 13 | } 14 | }; 15 | 16 | const load_options = () => 17 | chrome.storage.local.get(null, (items) => 18 | Object.keys(items).forEach(name => { 19 | const e = document.getElementById(name); 20 | if (!e) return; 21 | if (e.type == 'checkbox') e.checked = items[name]; 22 | else e.value = items[name]; 23 | }) 24 | ); 25 | 26 | async function init_options() { 27 | document.querySelectorAll('#option-setting input').forEach(e => { 28 | if (e.id == 'opt_bgcolor_hex') e.value = '#FF0000'; 29 | else if (e.id == 'opt_jsdr_cutoff') e.value = '15'; 30 | else if (e.id == 'opt_process_on_wishlist') e.checked = true; 31 | else if (e.type == 'checkbox') e.checked = false; 32 | else if (e.type == 'text') e.value = ''; 33 | else e.value = ''; 34 | }); 35 | await save_options(); 36 | load_options(); 37 | } 38 | 39 | const show_storage = async () => 40 | chrome.storage.local.get(null, (items) => console.log(items)); 41 | 42 | async function get_notice() { 43 | const stamp = (new Date()).getUTCMinutes(); 44 | const manifest = chrome.runtime.getManifest(); 45 | const version = manifest.version; 46 | const browser = manifest.browser_specific_settings?.gecko ? 'firefox' : 'chrome'; 47 | console.log(version, browser); 48 | const url = `${NOTICE_API}?stamp=${stamp}&version=${version}&browser=${browser}`; 49 | try { 50 | const response = await fetch(url); 51 | if (!response.ok) { 52 | console.error(`Network response was not ok: ${response.statusText}`); 53 | return; 54 | } 55 | const json = await response.json(); 56 | const noticeElement = document.getElementById('notice'); 57 | if (noticeElement) 58 | noticeElement.textContent = json?.result?.str ?? '現在お知らせはありません。'; 59 | } catch (error) { 60 | console.error(error, url); 61 | const noticeElement = document.getElementById('notice'); 62 | if (noticeElement) 63 | noticeElement.textContent = "お知らせを取得できませんでした。"; 64 | } 65 | } 66 | 67 | document.addEventListener('DOMContentLoaded', load_options); 68 | document.addEventListener('DOMContentLoaded', get_notice); 69 | 70 | [...document.querySelectorAll('input')]. 71 | forEach(e => e.addEventListener('change', save_options)); 72 | 73 | document.getElementById('close_button').addEventListener('click', () => window.close()); 74 | document.getElementById('init_button').addEventListener('click', () => init_options()); 75 | //document.getElementById('show_button').addEventListener('click', () => show_storage()); 76 | document.getElementById('clear_button').addEventListener('click', () => { 77 | if (window.confirm("すべての設定をリセットしますか?")) 78 | chrome.storage.local.clear(); 79 | }); 80 | -------------------------------------------------------------------------------- /firefox/options.js: -------------------------------------------------------------------------------- 1 | const NOTICE_API = 'https://www.listasin.net/api/notice.cgi'; 2 | 3 | const save_options = async () => { 4 | try { 5 | const promises = [...document.querySelectorAll('#option-setting input')].map(e => { 6 | const value = (e.type == 'checkbox') ? e.checked : e.value; 7 | return chrome.storage.local.set({[e.id]: value}); 8 | }); 9 | await Promise.all(promises); 10 | console.log("Options saved successfully"); 11 | } catch (error) { 12 | console.error("Failed to save options:", error); 13 | } 14 | }; 15 | 16 | const load_options = () => 17 | chrome.storage.local.get(null, (items) => 18 | Object.keys(items).forEach(name => { 19 | const e = document.getElementById(name); 20 | if (!e) return; 21 | if (e.type == 'checkbox') e.checked = items[name]; 22 | else e.value = items[name]; 23 | }) 24 | ); 25 | 26 | async function init_options() { 27 | document.querySelectorAll('#option-setting input').forEach(e => { 28 | if (e.id == 'opt_bgcolor_hex') e.value = '#FF0000'; 29 | else if (e.id == 'opt_jsdr_cutoff') e.value = '15'; 30 | else if (e.id == 'opt_process_on_wishlist') e.checked = true; 31 | else if (e.type == 'checkbox') e.checked = false; 32 | else if (e.type == 'text') e.value = ''; 33 | else e.value = ''; 34 | }); 35 | await save_options(); 36 | load_options(); 37 | } 38 | 39 | const show_storage = async () => 40 | chrome.storage.local.get(null, (items) => console.log(items)); 41 | 42 | async function get_notice() { 43 | const stamp = (new Date()).getUTCMinutes(); 44 | const manifest = chrome.runtime.getManifest(); 45 | const version = manifest.version; 46 | const browser = manifest.browser_specific_settings?.gecko ? 'firefox' : 'chrome'; 47 | console.log(version, browser); 48 | const url = `${NOTICE_API}?stamp=${stamp}&version=${version}&browser=${browser}`; 49 | try { 50 | const response = await fetch(url); 51 | if (!response.ok) { 52 | console.error(`Network response was not ok: ${response.statusText}`); 53 | return; 54 | } 55 | const json = await response.json(); 56 | const noticeElement = document.getElementById('notice'); 57 | if (noticeElement) 58 | noticeElement.textContent = json?.result?.str ?? '現在お知らせはありません。'; 59 | } catch (error) { 60 | console.error(error, url); 61 | const noticeElement = document.getElementById('notice'); 62 | if (noticeElement) 63 | noticeElement.textContent = "お知らせを取得できませんでした。"; 64 | } 65 | } 66 | 67 | document.addEventListener('DOMContentLoaded', load_options); 68 | document.addEventListener('DOMContentLoaded', get_notice); 69 | 70 | [...document.querySelectorAll('input')]. 71 | forEach(e => e.addEventListener('change', save_options)); 72 | 73 | document.getElementById('close_button').addEventListener('click', () => window.close()); 74 | document.getElementById('init_button').addEventListener('click', () => init_options()); 75 | //document.getElementById('show_button').addEventListener('click', () => show_storage()); 76 | document.getElementById('clear_button').addEventListener('click', () => { 77 | if (window.confirm("すべての設定をリセットしますか?")) 78 | chrome.storage.local.clear(); 79 | }); 80 | -------------------------------------------------------------------------------- /chrome/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Options 6 | 17 | 18 | 19 |
20 |

Options

21 |
22 |
23 |
24 |
25 | 29 | 33 | 37 | 50 | 51 |
52 |
53 | DEBUG用設定 54 |
55 | 59 | 63 | 67 | 71 | 75 | 76 | 77 | 78 |
79 |
80 |
81 |
82 |
83 | キセッペ (Kiseppe) 84 | powered by キンセリ 85 |
86 | ご意見・ご感想・ご要望・バグ報告などありましたら 87 | キセッペご意見箱 88 | へ! 89 |
90 |
91 |
92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /firefox/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Options 6 | 17 | 18 | 19 |
20 |

Options

21 |
22 |
23 |
24 |
25 | 29 | 33 | 37 | 50 | 51 |
52 |
53 | DEBUG用設定 54 |
55 | 59 | 63 | 67 | 71 | 75 | 76 | 77 | 78 |
79 |
80 |
81 |
82 |
83 | キセッペ (Kiseppe) 84 | powered by キンセリ 85 |
86 | ご意見・ご感想・ご要望・バグ報告などありましたら 87 | キセッペご意見箱 88 | へ! 89 |
90 |
91 |
92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2025-08-16 Tatsuwo 2 | 3 | * chrome/content-script.js: 4 | 7/14の修正が効かない事態に。 5 | HTML変更で、大元のKindle関連ページ判定ではじかれていた。 6 | Kindle 関連ページか否かの判定ロジックを変更: 7 | - 1. 上部横並びメニューの一番左の見出し「Kindle本」(マンガ以外) 8 | - 2. 上部横並びメニューの一番左のロゴ「amazon manga」(マンガ) 9 | 10 | 2025-07-14 Tatsuwo 11 | 12 | * chrome/content-script.js: 13 | - シリーズページで適用されなくなったので修正 14 | - storefront ページの shadowRoot のカルーセル処理が適応されなくなったので修正 15 | 16 | 2025-01-25 Tatsuwo 17 | 18 | * memo: v2.0.10 公式公開 19 | - github で Release (1/25) 20 | - chrome の審査通ったのは 1/25 21 | - firefox 審査通ったのは 1/24 22 | 23 | 2025-01-24 Tatsuwo 24 | 25 | * firefox/manifest.json: 2.0.10 26 | 27 | * chrome/manifest.json: 2.0.10 28 | 29 | 2025-01-23 Tatsuwo 30 | 31 | * chrome/content-script.js: 32 | 著者ページ(Author Page)のレイアウト変更に対応 33 | 34 | * chrome/options.js: 35 | コード調整 36 | 37 | * chrome/kiseppe.css: 38 | UI調整 39 | 40 | 2025-01-22 Tatsuwo 41 | 42 | * chrome/content-script.js: 43 | コード調整(最適化など) 44 | 45 | 2025-01-19 Tatsuwo 46 | 47 | * chrome/content-script.js: 48 | カルーセルにバッジ・背景色を表示するようにした。 49 | なお、ボタンはこれまで表示されていたので、追加でバッジと背景色。 50 | 動作にはオプションでの設定必要。 51 | 52 | 2025-01-16 Tatsuwo 53 | 54 | * chrome/options.html (Options): 55 | 項目「カルーセルへの処理を行う」をDEBUG用設定からメイン設定へ格上げ。 56 | 項目「価格推移グラフの表示のみ」をDEBUG用設定に格下げ。 57 | 58 | * chrome/content-script.js: 59 | kindle_asin_page(): 60 | ASINページ(個別作品ページ)でバッジが出なくなっていたので修正。 61 | kindle_shadowroot_carousel_component(): 62 | storefront ページの shadowRoot のカルーセルにボタンを出すようにした。 63 | (動作にはオプションでの設定必要) 64 | 65 | 2025-01-14 Tatsuwo 66 | 67 | * memo: v2.0.9 公式公開 68 | - github で Release (1/14) 69 | - chrome の審査通ったのは 1/15 70 | - firefox 審査通ったのは 1/14 71 | 72 | 2025-01-13 Tatsuwo 73 | 74 | * chrome/content-script.js: 75 | ウィッシュリストのグリッド表示でバッジ・ボタン・背景色を出せるようにした。 76 | 結構強引なやり方なのでアマゾン側でのちょっとしたレイアウト変更で影響受ける可能性大。 77 | 78 | * memo: プルリク。マージ。 79 | Foo-x さん、ありがとうございます。 80 | 81 | fix: to show wishlist discount rate badge by Foo-x · Pull Request #4 · yto/kiseppe 82 | https://github.com/yto/kiseppe/pull/4 83 | > ウィッシュリストの仕様変更により割引率が表示されなくなった問題を修正しました。 84 | 85 | 2024-12-24 Tatsuwo 86 | 87 | * memo: v2.0.8 公式公開 88 | - github で Release 89 | - chrome の審査通ったのは 12/25 90 | - firefox 審査通ったのは 12/24 91 | 92 | * firefox/manifest.json: v2.0.8 93 | 94 | * chrome/manifest.json: v2.0.8 95 | 96 | * firefox/content-script.js: 97 | chrome のをコピー 98 | 99 | * chrome/content-script.js: 100 | 「購入前に必ず公式情報をご確認ください」という旨のTIPSを追加 101 | 102 | 2024-12-23 Tatsuwo 103 | 104 | * chrome/content-script.js: 105 | 検索結果ページで kindleunlimited の作品が 0 円扱い(100%オフ)表示になってしまう不具合。 106 | 検索結果ページに KU 作品の価格が表示されなくなったのが原因。 107 | もともとこのような場合にはAPIの結果だけを表示する方針だったが、コードが意図通りではなかった。 108 | price を undefined にするようにコードを修正。 109 | 110 | 2024-12-22 Tatsuwo 111 | 112 | * chrome/content-script.js: 113 | - Octopus Component: 処理が機能しなくなっていた → 修正 114 | - Wishlist: observer が機能していない → 修正 115 | - [断念] Wishlist: グリッド表示でボタン等出ない → Kindleか否かの判定できず 116 | - [断念] TOP: キンドルトップページのカルーセルへの対応 → shadow-root... 117 | 118 | 2024-07-02 Tatsuwo 119 | 120 | * memo: v2.0.7 公式公開 121 | - github で Release 122 | - chrome firefox 審査通ったのは 7/2 123 | 124 | * firefox/manifest.json: v2.0.7 125 | 126 | * chrome/manifest.json: v2.0.7 127 | 128 | 2024-07-01 Tatsuwo 129 | 130 | * firefox/manifest.json: v2.0.6.9 131 | 132 | * chrome/manifest.json: v2.0.6.9 133 | 134 | 2024-06-29 Tatsuwo 135 | 136 | * firefox/content-script.js: 137 | chrome と同様 138 | 139 | * chrome/content-script.js: 140 | 一部の環境で、 141 | Kindle 書籍の検索結果表示が、 142 | 1行1作品の横長カード表示から、 143 | 1行複数作品の縦長ボックス表示になったみたい。 144 | 今後全部このレイアウトになるのかもしれない。 145 | まだ場合によっては前のレイアウトが出ることもある。 146 | このレイアウト変更により、 147 | Kiseppe による実質割引率の表示バッジが出なくなった。 148 | 旧レイアウトの処理は残したままで、 149 | 新レイアウトにも対応するように修正。 150 | 151 | 2024-06-22 Tatsuwo 152 | 153 | * memo: v2.0.6 公式公開 154 | chrome firefox 審査通ったのは 6/21 155 | 156 | 2024-06-21 Tatsuwo 157 | 158 | * firefox/content-script.js: 159 | chrome と同じ 160 | 161 | * chrome/content-script.js: 162 | シリーズページで、 163 | Kindle 関連ページの判定方法 164 | (上のメニューバーの一番左に "Kindle" があるかどうか) 165 | が機能しなくなっていた。 166 | メニューバーに「amazon manga」が表示されるようになった影響。 167 | メニューバーに "Fliptoon" が含まれればOKにするようにした。 168 | 169 | * firefox/manifest.json: v2.0.6 170 | 171 | * chrome/manifest.json: v2.0.6 172 | 173 | * memo: 今日から v2.0.6 の作業を開始する 174 | - シリーズページで機能しなくなったので調査&対応 175 | 176 | * memo: v2.0.5 公式公開(1月にやるのを忘れていた模様) 177 | 178 | 2024-01-26 Tatsuwo 179 | 180 | * chrome/kiseppe.css: 181 | .kiseppe-pg-btn の z-index を調整 (10000 => 1) 182 | 183 | 2023-12-28 Tatsuwo 184 | 185 | * memo: v2.0.4 公式公開 186 | 187 | 2023-12-25 Tatsuwo 188 | 189 | * chrome/content-script.js: 190 | window.addEventListener('message', function(e) { ... }) 191 | を insert_price_graph() から外に出した。 192 | insert_price_graph() 以外からも使うため。 193 | 194 | 2023-12-17 Tatsuwo 195 | 196 | * chrome/content-script.js: 197 | スマホ表示 (SP page) への対応に着手。 198 | ほしい物リスト、ASIN ページを対応させた。 199 | ついでに、ページ種類判定部分、ページ処理部分も直す。 200 | 201 | 2023-12-10 Tatsuwo 202 | 203 | * chrome/content-script.js: 204 | 経緯: 205 | 個別作品ページでの価格表示部分のレイアウトが変更されていた。右カラムにまとめて表示されるようになった。そのため、個別作品ページで、今表示されている価格と還元ポイントが取得できず、価格推移グラフに最新値としての反映ができない。 206 | 対応策は、content-script.js の extract_price_and_point() かその呼び出し元の修正。現在は try-catch 内でエラー発生するのみで kiseppe の動作には影響はない。 207 | 208 | 実装: 209 | kindle_asin_page() で 210 | extract_price_and_point() を呼び出すときに渡すエレメントを変更。 211 | kindleunlimited の "¥0" をとってしまう問題もあったが、解決。 212 | 213 | 2023-12-07 Tatsuwo 214 | 215 | * chrome/content-script.js: retrun => return (はずかしいタイポ) 216 | 217 | 2023-11-24 Tatsuwo 218 | 219 | * memo: v2.0.3 公式公開 220 | Chrome も Firefox も審査は当日通る。 221 | 222 | * chrome/content-script.js: extract_price_and_point() 223 | いまさらながらエラーハンドリング追加。 224 | これらに対処: 225 | - キーワード検索で検索結果が少なくKindle以外のカテゴリの商品が表示されるときに処理が止まるケース 226 | - ほしい物リストで配信停止作品があると処理が止まるケース 227 | 228 | 2023-11-23 Tatsuwo 229 | 230 | * memo: v2.0.2 公式公開 231 | Chrome も Firefox も審査は即日通る。 232 | 233 | * chrome/kiseppe.css (.kiseppe-series-sale-badge): 234 | add "color: red;" (for Windows Chrome) 235 | 236 | * chrome/content-script.js: extract_price_and_point() 237 | - ASINページで紙の本の値を見に行かないようにした。 238 | - KU のときの価格の取り方を修正 239 | - Ex. https://www.amazon.co.jp/dp/B09FPWBCLL 240 | > '.selected' 241 | > Kindle版 (電子書籍) 242 | > ¥0 Kindle Unlimited 243 | > ¥11 244 | > '.unselected' 245 | > コミック (紙) 246 | > ¥306 247 | > 獲得ポイント: 3pt 248 | 249 | * chrome/content-script.js: calc_jsdr() 250 | 実質割引率が100%でも実質価格1円以上のときがあり、そのときは99%にする。 251 | なお、API 側でもこの処理を行っていない箇所があったので修正した。 252 | 253 | * chrome/options.js, chrome/options.html: 254 | 「ほしい物リスト」のキンドル本に割引情報を表示するオプションをデフォルトONにする 255 | https://twitter.com/kinselist/status/1726798690747404502 256 | 257 | 2023-11-17 Tatsuwo 258 | 259 | * chrome/content-script.js: 260 | 「167%オフ」が出た報告あり。 261 | その対応策として、 262 | get_jsdr() で実質割引率が 0 以上 100 以下でない場合は一律 0 にする。 263 | 264 | * chrome/options.html: 265 | - キセッペご意見箱へのリンク 266 | 267 | 2023-11-12 Tatsuwo 268 | 269 | * chrome/content-script.js: 270 | - access_api() => access_api_params(params) 271 | - User ID for Kiseppe API 272 | 273 | * chrome/options.js: User ID for Kiseppe API 274 | 275 | * chrome/options.html: User ID for Kiseppe API 276 | 277 | 2023-11-07 Tatsuwo 278 | 279 | * memo: v2.0.1 公式公開 280 | - Firefox ADD-ONS: 審査依頼後一瞬で審査通る (11/6) 281 | - Chrome ウェブストア: 11/7 の午後に審査通る、ただし日付は 11/6 282 | 283 | 2023-11-06 Tatsuwo 284 | 285 | * release: v2.0.1 286 | 審査に出す 287 | 288 | 2023-11-03 Tatsuwo 289 | 290 | * chrome/content-script.js: new wishlist_component(). 291 | ほしい物リストにも対応(オプション) 292 | 293 | * chrome/content-script.js: 294 | Kindleページ判定失敗例: 295 | NG https://www.amazon.co.jp/dp/B07DHFQCTJ/?sr=8-1 296 | OK https://www.amazon.co.jp/dp/B07DHFQCTJ 297 | 修正: 298 | OLD: document.getElementById('nav-search-label-id'); 299 | NEW: document.querySelector('#nav-subnav .nav-a-content') 300 | 301 | 2023-11-01 Tatsuwo 302 | 303 | * release: v2.0.0 304 | Chrome 審査通る(11/1) 305 | Firefox 審査通る(10/30) 306 | 307 | 2023-10-30 Tatsuwo 308 | 309 | * release: v2.0.0-rc 310 | 審査に出した版。 311 | 312 | 2023-10-29 Tatsuwo 313 | 314 | * release: v2.0.0-beta.2 315 | 316 | * memo: カルーセルへの処理(カルーセル内の各作品にグラフ表示ボタンを設置する処理)をオプションとした。 317 | デフォルトでは表示しない (false)。 318 | まだ見ぬケースで暴走する可能性を危惧。 319 | storage.local の opt_process_on_carousel で制御。 320 | 321 | * chrome/manifest.json: これ要るのか否か。 322 | >> 323 | "content_security_policy": { 324 | "extension_pages": "script-src 'self'; object-src 'self'; connect-src https://www.listasin.net/api/;", 325 | "content_scripts": "default-src 'self'; connect-src https://www.listasin.net/api/;" 326 | }, 327 | << 328 | 今は無しにしておいてリジェクトされたらまた考える。 329 | 330 | * memo: ChatGPT にコードレビューしてもらい、納得いったものを採用 331 | 332 | 2023-10-26 Tatsuwo 333 | 334 | * chrome/content-script.js: API仕様変更。セール判定精度向上のため、検索結果ページなどでも API のパラメータに価格・ポイント還元情報を追加。 335 | 336 | * chrome/options.html: console.log, console.group* のON/OFF 337 | 338 | * chrome/content-script.js: console.log, console.group* のON/OFF 339 | 340 | 2023-10-25 Tatsuwo 341 | 342 | * release: v2.0.0-beta 343 | 344 | * chrome/content-script.js: API の URL を本番用 (v2 用) のに変更 345 | 346 | * version: 347 | manifest.json の version 1.99.1.0 は release の 2.0.0-beta と同等。 348 | (manifest.json の version には数字と"."しか書けない) 349 | 例: 350 | 1.99.0.3 = 2.0.0-alpha.3 351 | 1.99.1.0 = 2.0.0-beta 352 | 1.99.1.2 = 2.0.0-beta.2 353 | 1.99.2.0 = 2.0.0-rc 354 | 2.0.0 = 2.0.0 355 | 2.0.1 = 2.0.1 356 | 2.1.0 = 2.1.0 357 | 2.99.0.0 = 3.0.0-alpha 358 | 359 | 2023-10-24 Tatsuwo 360 | 361 | * memo: untabify, change API urls 362 | 363 | 2023-10-23 Tatsuwo 364 | 365 | * debug-message.*: ここから先は使わないでよさそうなので削除 366 | 367 | 2023-10-22 Tatsuwo 368 | 369 | * chrome/options.js: 運営側からのお知らせを表示する仕組みを入れた。 370 | お知らせは専用 API から fetch で取得し innerHTML に挿入する方式。 371 | 372 | * memo: chrome と firefox での要素の表示ON/OFF 373 | >> 374 | .ch-only { display: none; } 375 | _:lang(x)::-internal-media-controls-overlay-cast-button, .ch-only { display: inline } 376 | .ff-only { display: none; } 377 | _:lang(x)::-moz-placeholder, .ff-only { display: inline } 378 | << 379 | options.html で場合分けをしようと思ったがやめたのでここにメモだけしておく。 380 | 381 | 2023-10-21 Tatsuwo 382 | 383 | * README.md: fix 384 | 385 | * CHANGES.md: new. リリースノートを書いていくファイル。 386 | 387 | 2023-10-20 Tatsuwo 388 | 389 | * release: v2.0.0-alpha.3 390 | 391 | * chrome/manifest.json: options を popup でも使うようにした 392 | 393 | * chrome/content-script.js: Kindle Unlimited が0円価格と認識されてしまい 394 | 実質100%オフと表示されるバグ。 395 | これまでの KU 判定: e.querySelector('.a-icon-kindle-unlimited') 396 | 修正1: '.a-icon-kindle-unlimited, .apex-kindle-unlimited-badge' 397 | 修正2: '[class*=kindle-unlimited]' 398 | 399 | 2023-10-19 Tatsuwo 400 | 401 | * release: v2.0.0-alpha.2 402 | Release v2.0.0-alpha.2リリース · yto/kiseppe 403 | https://github.com/yto/kiseppe/releases/tag/v.2.0.0-alpha.2 404 | 405 | * firefox: chrome/ のソースをコピーしてテスト。 406 | セール作品の背景グラデーションが意図通りでなかったので、 407 | どちらでも同じになるように修正。 408 | OLD: linear-gradient(${rgba}, 90%, white) 409 | NEW: linear-gradient(${rgba}, 90%, rgba(${rgb},0) 410 | 411 | 2023-10-18 Tatsuwo 412 | 413 | * chrome/content-script.js: option対応 414 | 415 | * chrome/options.js: new 416 | 417 | * chrome/options.html: new 418 | 419 | 2023-10-15 Tatsuwo 420 | 421 | * release: v2.0.0-alpha 422 | 423 | * chrome/debug-message.css: new. - 424 | 425 | * chrome/debug-message.js: new. 簡易デバグメッセージ表示。 426 | デベロッパーツールを立ち上げる前のチェック用途。 427 | 428 | * chrome/content-script.js: 429 | - ASIN page にもセール価格時にバッヂ表示・背景色変更 430 | - その他いろいろ 431 | 432 | 2023-10-11 Tatsuwo 433 | 434 | * chrome/content-script.js: iroiro 435 | 436 | 2023-10-09 Tatsuwo 437 | 438 | * chrome/content-script.js: 連休作業の反映 439 | 440 | 2023-10-03 Tatsuwo 441 | 442 | * chrome/content-script.js: iroiro 443 | 444 | 2023-09-30 Tatsuwo 445 | 446 | * chrome/content-script.js: iroiro 447 | 448 | 2023-09-28 Tatsuwo 449 | 450 | * chrome/content-script.js: iroiro 451 | 452 | 2023-09-26 Tatsuwo 453 | 454 | * chrome/content-script.js: 455 | - new function extract_price_and_point() 456 | - new function get_jsdr() 457 | - new function generate_callback_ex => 指定ノードを exclude して observe 458 | 459 | 2023-09-24 Tatsuwo 460 | 461 | * chrome/kiseppe.css: new! 462 | 463 | * chrome/content-script.js: 464 | - MutationObserver 完全に理解した() 465 | - 処理する対象を page と component にわけて整理した 466 | 467 | 2023-09-23 Tatsuwo 468 | 469 | * chrome/content-script.js: 470 | - Author page 対応 471 | - プログラミング構成変更 472 | 473 | 2023-09-21 Tatsuwo 474 | 475 | * chrome/manifest.json: background 関連の削除 476 | 477 | * chrome/background.js: これいらなかったっぽい 478 | 479 | * chrome/content-script.js: 480 | - horizontal 整理、manga-store 対応など 481 | - grid 30 整理 482 | 483 | 2023-09-20 Tatsuwo 484 | 485 | * chrome/content-script.js: ランキングページにも対応。 486 | &その他いろいろ。 487 | 488 | 2023-09-19 Tatsuwo 489 | 490 | * chrome/content-script.js: 491 | - bug fix: AISNページで複数グラフがでることあり。 executeScript にした影響。 492 | => すでにグラフが表示されてたら帰る処理を追加 493 | - シリーズ「シリーズにセール作品あり」の表示 494 | - API の仕様を変えたので対応 495 | 496 | 2023-09-18 Tatsuwo 497 | 498 | * chrome/background.js: 2.0 に向けて。 499 | 500 | * chrome/manifest.json: 2.0 に向けて。 501 | 502 | * chrome/content-script.js: 2.0 に向けて。 503 | 検索結果ページ、まとめページ、特設ページなどに 504 | - グラフ表示ボタン 505 | - 実質割引率の表示と率に応じた背景色変更 506 | 507 | 2023-08-29 Tatsuwo 508 | 509 | * chrome/content-script.js: memo: iframe の読み込み終了後に contentWindow で高さを取得する方式は使えない。 510 | > let new_elm = document.createElement('iframe'); 511 | > new_elm.style.width = '100%'; 512 | > new_elm.id = 'kiseppe'; 513 | > new_elm.src = url; 514 | > new_elm.onload = function(e) { 515 | > console.log('loaded iframe'); 516 | > console.log(e.target.contentWindow.document); 517 | > // CORSがらみのエラーがでる 518 | > }; 519 | 520 | 2023-08-28 Tatsuwo 521 | 522 | * chrome/manifest.json: firefox で動くようにしたら chrome で動かなくなっていたので直した。 523 | 524 | * memo: chrome と firefox とでコードをわけた。 525 | それぞれ用のディレクトリ作って分類。 526 | promotion 画像は images ディレクトリへ。 527 | 528 | 2023-04-02 Tatsuwo 529 | 530 | * v1.0.2: Firefox の審査通ったので 1.0.2 に (リリース忘れてた) 531 | 532 | 2023-03-19 Tatsuwo 533 | 534 | * README.md: Firefox についての記述 535 | 536 | * manifest.json: ff用の記述 537 | "browser_specific_settings": { "gecko": { "id": "kiseppe_ff@example.com" } }, 538 | 539 | * memo: Firefox 拡張機能の審査通る。1回で済んだ。 540 | Kiseppe - Kindle Price Chart – 🦊 Firefox (ja) 向け拡張機能を入手 541 | https://addons.mozilla.org/ja/firefox/addon/kiseppe-price-chart-kindle/ 542 | 543 | ffでの問題点。 544 | ff だと iframe の src 側の高さが想定よりも高くなる。 545 | iframe 内に描画される前の src 側の width が 90px とかになっている模様。 546 | 幅が狭くなる分、高くなるわけで。 547 | src 側 (api 出力) で height を 400 max にすることに。 548 | 549 | 2023-03-18 Tatsuwo 550 | 551 | * memo: Firefox 拡張機能の審査待ち。 552 | 553 | 2023-03-16 Tatsuwo 554 | 555 | * v1.0.1: 審査通ったので 1.0.0 から 1.0.1 に 556 | 557 | * memo: 3回目審査通って Chrome ウェブストアで公開された。 558 | Kiseppe - Price Chart for Amazon Japan Kindle - Chrome ウェブストア 559 | https://chrome.google.com/webstore/detail/kiseppe-price-chart-for-a/jhmbgbjpbiiklgmfabbcldoddlljplle 560 | 561 | 2023-03-15 Tatsuwo 562 | 563 | * memo: 3回目審査 564 | 特に2回目の reject で言われていないけど、iframe はリモートコードではないみたい。 565 | なのでリモートコード不使用に変更して申請。 566 | 567 | * manifest.json: 568 | delete: "permissions": ["activeTab", "scripting"], 569 | delete: "action": {...}, 570 | 571 | * memo: 2回目審査 rejected. なにやら私が勘違いしていた模様。 572 | > 違反: 次の権限をリクエストしていますが使用していません。 573 | > activeTab 574 | > scripting 575 | > 修正方法: 上記の権限を削除します 576 | 577 | 2023-03-14 Tatsuwo 578 | 579 | * memo: 2回目審査 580 | 581 | * manifest.json: 日本語を英語にした 582 | 583 | 2023-03-13 Tatsuwo 584 | 585 | * memo: Chrome 審査 reject される 586 | 587 | * content-script.js: 588 | "Kindle本のページであるか" のチェック部分を少し丁寧にした。 589 | 具体的には 590 | > if (! document.getElementById('nav-search-label-id')) return; 591 | を追加。 592 | 593 | * v1.0.0: release 594 | 595 | 2023-03-12 Tatsuwo 596 | 597 | * memo: github で公開。Chrome ウェブストアに審査出す。 598 | 599 | -------------------------------------------------------------------------------- /chrome/content-script.js: -------------------------------------------------------------------------------- 1 | // Kiseppe's target pages 2 | // 3 | // Pages: 4 | // - Kindle ASIN Page 5 | // - insert:[price-graph-iframe][item-jsdr][series-jsdr] 6 | // - observe 7 | // - wait 8 | // - Kindle Series Page 9 | // - insert:[price-graph-button][item-jsdr][series-jsdr] 10 | // - observe 11 | // - Kindle Search Page 12 | // - insert:[price-graph-button][item-jsdr][series-jsdr] 13 | // - observe 14 | // - wait 15 | // - Kindle Author Page 16 | // - Kindle Author Component 17 | // - Kindle Carousel Component 18 | // - observe 19 | // - Kindle Ranking Page 20 | // - insert:[price-graph-button][item-jsdr] 21 | // - observe 22 | // - Kindle Grid30 Page 23 | // - insert:[price-graph-button][item-jsdr] 24 | // - Kindle Octopus Page 25 | // - Kindle Carousel Component 26 | // - Kindle Octopus Component 27 | // - Kindle Grid12 Page 28 | // - Kindle Carousel Component 29 | // - Kindle Grid12 Component 30 | // - observe 31 | // - Kindle Store Front Page 32 | // - Kindle shadowRoot Carousel Component 33 | // - observe 34 | // - Kindle Manga Store Page 35 | // - Kindle Carousel Component 36 | // - observe 37 | // - Kindle Page (others) 38 | // - Kindle Carousel Component 39 | // - Kindle Page (特別設定なし) 40 | // - multiby https://www.amazon.co.jp/kindle-dbs/multibuy?basketId=Gz2UUPLE 41 | // - stores page https://www.amazon.co.jp/stores/page/EF53E35F-2FAF-49AC-B191-257E764F2703 42 | // - Wishlist Page 43 | // - insert:[price-graph-iframe][item-jsdr] 44 | // - observe 45 | // - wait 46 | // 47 | // Components: 48 | // - Kindle Author Component 49 | // - [price-graph-button][item-jsdr] 50 | // - Kindle Octopus Component 51 | // - [price-graph-button][item-jsdr][series-jsdr] 52 | // - Kindle Grid12 Component 53 | // - [price-graph-button][item-jsdr] 54 | // - Kindle Carousel Component 55 | // - General Carousel 56 | // - [price-graph-button] 57 | // - Manga Store Ranking Carousel 58 | // - [price-graph-button] 59 | // - Kindle shadowRoot Carousel Component 60 | // - Kindle Store Front Page 61 | // - [price-graph-button] 62 | // 63 | // Words 64 | // - KS means Kiseppe or Kinseli 65 | // - JSDR means real discount rate (実質割引率) 66 | 67 | const KS_IF_API = 'https://www.listasin.net/api/0200/chex/'; // iframe 68 | let KS_JD_API = 'https://www.listasin.net/api/0200_jd.cgi?asins='; // json 69 | const DEBUG_API = 'https://www.listasin.net/api/debug-logging.cgi?asins='; 70 | let JSDR_CUTOFF = 15; 71 | let PROCESS_ON_CAROUSEL = false; 72 | let storage_items = {}; 73 | 74 | let console_groupCollapsed = () => {}; 75 | let console_groupEnd = () => {}; 76 | let console_log = () => {}; 77 | let console_count = () => {}; 78 | 79 | 80 | //////////////////////////////////////////////////////////////// 81 | // Determine page type and assign processing 82 | // ページの種類を判定して処理を割り当てる 83 | // Use an observer for pages with dynamically changing content 84 | // 動的に内容が変化するページには observer を使う 85 | async function main() { 86 | 87 | //// スマホ表示なのかの判定 88 | const sma = document.querySelector('html').classList.contains('a-touch'); 89 | if (sma) console.log('Kiseppe: smart phone view'); 90 | 91 | //// Kindle 関連ページじゃないときは何もせずに終わる 92 | let ok_flag = 0; 93 | let is_asin_page = 0; 94 | let is_wishlist_page = 0; 95 | 96 | //// Kindle 関連ページか否かの判定 97 | // 1. 上部横並びメニューの一番左の見出し「Kindle本」(マンガ以外) 98 | let chk = document.querySelector('#nav-subnav a[aria-label]')?.textContent; 99 | // 2. 上部横並びメニューの一番左のロゴ「amazon manga」(マンガ) 100 | chk += document.querySelector('#nav-subnav a')?.innerHTML; 101 | if (/Kindle|Amazon マンガ/.test(chk)) ok_flag = 1; 102 | 103 | if (document.querySelector('#tmm-grid-swatch-KINDLE,#tmm-grid-swatch-OTHER')?.classList.contains('selected')) { 104 | // ASIN page 判定 (PC/SP 共通) 105 | is_asin_page = 1; 106 | console.log('judged: PC/SP ASIN page'); 107 | } else if (/ほしい物リスト/.test( 108 | document.querySelector('meta[property="og:title"]')?.getAttribute('content') 109 | )) { 110 | // ほしい物リストページの判定 (PC/SP 共通) 111 | is_wishlist_page = 1; 112 | console.log('judged: PC/SP wishlist'); 113 | } else if (ok_flag) { 114 | // Kindle 関連ページの判定 115 | console.log('judged: a page in kindle store'); 116 | } else { 117 | // Kindle 関連ページではないので以降の処理は行わない 118 | return; 119 | } 120 | 121 | // options 122 | storage_items = await new Promise(r => chrome.storage.local.get(null, r)). 123 | catch(error => console.error(error)); 124 | if (storage_items?.opt_use_debug_api == true) 125 | KS_JD_API = DEBUG_API; 126 | if (storage_items?.opt_jsdr_cutoff) 127 | JSDR_CUTOFF = Number(storage_items.opt_jsdr_cutoff); 128 | if (storage_items?.opt_process_on_carousel) 129 | PROCESS_ON_CAROUSEL = true; 130 | const isDebugMode = storage_items?.opt_activate_console_log ?? false; 131 | if (isDebugMode) { 132 | console_groupCollapsed = (...s) => console.groupCollapsed(...s); 133 | console_groupEnd = (...s) => console.groupEnd(...s); 134 | console_log = (...s) => console.log(...s); 135 | console_count = (...s) => console.count(...s); 136 | } 137 | 138 | console_log("Kiseppe: start processing on Kindle related page") 139 | console_log("options", storage_items) 140 | 141 | // callback function for observer 142 | const config = { childList: true, subtree: true }; 143 | const generate_callback = (e, f) => async function (mutations, observer) { 144 | let shouldRun = false; 145 | for (let mutation of mutations) { 146 | if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) { 147 | shouldRun = true; 148 | break; 149 | } 150 | } 151 | if (!shouldRun) return; 152 | 153 | observer.disconnect(); 154 | console_log('stop observation and do something'); 155 | try { 156 | await f(); 157 | } catch (error) { 158 | console.error('Error:', error); 159 | } 160 | console_log('restart observation'); 161 | observer.observe(e, config); 162 | }; 163 | const generate_callback_ex = (e, qs, f) => async function (mutations, observer) { 164 | const exs = new Set([...e.querySelectorAll(qs)]); 165 | let is_mutation_detected = mutations.some(mu => !exs.has(mu.target)); 166 | if (!is_mutation_detected) return; 167 | const f2 = generate_callback(e, f); 168 | f2(mutations, observer); 169 | }; 170 | 171 | if (is_wishlist_page) { 172 | 173 | if (!storage_items?.opt_process_on_wishlist) return; 174 | 175 | await wishlist_page(); 176 | 177 | const e = document.querySelector('div#wl-item-view, ul#awl-list-items'); 178 | // PC page: ul#g-items=>div#... / SP page: ul#awl-list-items 179 | console.assert(e); 180 | if (!e) return; 181 | const callback = generate_callback(e, wishlist_page); 182 | const observer = new MutationObserver(callback); 183 | observer.observe(e, config); 184 | 185 | } // case: /Kindle/.test(chk) === true 186 | //else if (document.getElementById('ASIN')) { 187 | else if (is_asin_page) { 188 | 189 | console_log("kiseppe: here is Kindle ASIN Page"); 190 | kindle_asin_page(); 191 | if (storage_items?.opt_asin_page_only) return; 192 | if (!PROCESS_ON_CAROUSEL) return; 193 | 194 | await kindle_carousel_component(); 195 | console_log("wait a few seconds"); 196 | await sleep(1500); 197 | console_log("ok, go!"); 198 | 199 | // - ignore carousel components without kindle books 200 | // - observe only [id=dp-container] 201 | const e = document.getElementById('dp-container'); 202 | console.assert(e, "Element with id=dp-container not found"); 203 | // - ignore countdown timer 204 | // - [id*="Timer"] : 期間限定キャンペーン(right price box) 205 | // - [data-endtimeseconds], [class*="timer"] : 特別オファー 206 | const callback = generate_callback_ex( 207 | e, 208 | '[id*="Timer"], [class*="timer"]', 209 | kindle_carousel_component 210 | ); 211 | const observer = new MutationObserver(callback); 212 | observer.observe(e, config); 213 | 214 | } else if (storage_items?.opt_asin_page_only) { 215 | 216 | // 当該オプション指定されていれば ASIN ページ以外の処理は行わない 217 | return; 218 | 219 | } else if (document.querySelector( 220 | 'div[id="browse-views-area"] div[class*="browse-clickable-item"]' 221 | )) { 222 | 223 | console_log("kiseppe: here is Kindle Grid30 Page"); 224 | kindle_grid30_page(); 225 | 226 | } else if (document.querySelector( 227 | '[data-card-metrics-id*="octopus-search-result-card_"' 228 | )) { 229 | 230 | console_log("kiseppe: here is Kindle Octpus Page"); 231 | kindle_octopus_component(); 232 | if (!PROCESS_ON_CAROUSEL) return; 233 | kindle_carousel_component(); 234 | 235 | } else if (/\/author\//.test(location.href)) { 236 | 237 | console_log("kiseppe: here is Kindle Author Page"); 238 | kindle_author_component(); 239 | if (!PROCESS_ON_CAROUSEL) return; 240 | await kindle_carousel_component(); 241 | 242 | //const e = document.querySelector('#authorPageBooks'); 243 | const e = document.querySelector('[data-card-metrics-id^=smart-catalog-card_author'); 244 | if (!e) return; 245 | console.assert(e); 246 | const callback = generate_callback(e, kindle_carousel_component); 247 | const observer = new MutationObserver(callback); 248 | observer.observe(e, config); 249 | 250 | } else if (document.querySelector('div[class*="result-section"]') && 251 | document.querySelector('div[id=search-results]')) { 252 | 253 | console_log("kiseppe: here is Grid12 Page"); 254 | await kindle_grid12_component(); 255 | 256 | const e = document.querySelector('div[id=search-results]').parentNode; 257 | console.assert(e); 258 | const callback = generate_callback(e, kindle_grid12_component); 259 | const observer = new MutationObserver(callback); 260 | observer.observe(e, config); 261 | 262 | if (!PROCESS_ON_CAROUSEL) return; 263 | kindle_carousel_component(); 264 | 265 | } else if (document.querySelector( 266 | '[id=series-childAsin-widget], [data-parent-asins]' 267 | //'[data-asin][data-entity-type=collection]' 268 | )) { 269 | 270 | console_log("kiseppe: here is Kindle Series Page"); 271 | await kindle_series_page(); 272 | 273 | const e = document.querySelector('div[id=series-childAsin-widget]'); 274 | // シリーズASINはあるが作品はない場合 ex. B09YL554L6 275 | if (!e) return; 276 | 277 | const callback = 278 | generate_callback_ex(e, '#countdown_timer', kindle_series_page); 279 | // #countdown_timer : 終了までXXXXX秒 280 | const observer = new MutationObserver(callback); 281 | observer.observe(e, config); 282 | 283 | } else if (document.querySelector('div#nav-subnav[data-category="digital-text"]') && 284 | document.querySelector('div#search')) { 285 | 286 | console_log("kiseppe: here is Kindle Search Page"); 287 | await kindle_search_page(); 288 | 289 | const e = document.querySelector('div#search'); 290 | console.assert(e); 291 | const callback = generate_callback(e, kindle_search_page); 292 | const observer = new MutationObserver(callback); 293 | observer.observe(e, config); 294 | 295 | } else if (/(new-releases|bestsellers|movers-and-shakers)\/digital-text/.test(location.href)) { 296 | 297 | console_log("kiseppe: here is Kindle Ranking Page"); 298 | await kindle_ranking_page(); 299 | 300 | const e = document.querySelector('div.p13n-desktop-grid'); 301 | console.assert(e); 302 | const callback = generate_callback(e, kindle_ranking_page); 303 | const observer = new MutationObserver(callback); 304 | observer.observe(e, config); 305 | 306 | } else if (/kindle-dbs\/storefront/.test(location.href)) { 307 | 308 | console_log("kiseppe: here is Kindle Store Front Page"); 309 | 310 | if (!PROCESS_ON_CAROUSEL) return; 311 | await kindle_shadowroot_carousel_component(); 312 | 313 | const e = document.querySelector('html'); 314 | console.assert(e); 315 | const callback = generate_callback(e, kindle_shadowroot_carousel_component); 316 | const observer = new MutationObserver(callback); 317 | observer.observe(e, config); 318 | 319 | } else if (/manga-store/.test(location.href)) { 320 | 321 | console_log("kiseppe: here is Kindle Manga Store Page"); 322 | 323 | if (!PROCESS_ON_CAROUSEL) return; 324 | await kindle_carousel_component(); 325 | 326 | const e = document.querySelector('.msw-page'); 327 | console.assert(e); 328 | const callback = generate_callback_ex( 329 | e, 330 | 'div[data-csa-c-painter="banner-carousel-cards"]', 331 | kindle_carousel_component 332 | ); 333 | const observer = new MutationObserver(callback); 334 | observer.observe(e, config); 335 | 336 | } else if (document.querySelector( 337 | '#nav-subnav[data-category=digital-text]' 338 | )) { 339 | 340 | console_log("kiseppe: here is Kindle Store Page (Others)"); 341 | 342 | if (!PROCESS_ON_CAROUSEL) return; 343 | kindle_carousel_component(); 344 | 345 | } else { 346 | console_log("kiseppe: nothing"); 347 | } 348 | } 349 | 350 | 351 | //////////////////////////////////////////////////////////////// 352 | // functions to process page or component (part of HTML page) 353 | // ページやコンポーネント(ページ内の一部分)ごとの処理を行う関数たち 354 | // 355 | // 1. parse HTML 356 | // - get ASINs, title, series ASIN 357 | // - put graph button 358 | // 2. API access 359 | // - access Kiseppe's Web API 360 | // - get result (JSON) 361 | // 3. display jsdr information 362 | // - put jsdr badge, change background color 363 | 364 | //// Kindle ASIN page 365 | // Ex. https://www.amazon.co.jp/dp/B0C5QMW1JY 366 | async function kindle_asin_page() { 367 | //const asin = document.getElementById('ASIN').value; // PC page only 368 | const asin = (document.URL.match(/B[0-9A-Z]{9}/) || [])[0] ?? ''; // Common 369 | if (!/^B[0-9A-Z]{9}$/.test(asin)) return; 370 | 371 | //// get the price and the point back from this page 372 | //const pinfo = extract_price_and_point(document.querySelector('#MediaMatrix')); 373 | const pinfo = extract_price_and_point( 374 | //document.querySelector('#mediamatrix_feature_div') 375 | document.querySelector('#mediamatrix_feature_div .selected') 376 | ); 377 | 378 | // call kiseppe 1.0 (kiseppe1.0::main() => insert_price_graph()) 379 | insert_price_graph(asin, pinfo); 380 | if (storage_items?.opt_asin_page_only) return; 381 | 382 | // put price graph button (since 2023/12) 383 | const te = document.querySelector('h1#title'); // PC page 384 | const btn = build_price_graph_dialog(asin, te?.textContent, pinfo); 385 | if (te && btn) { 386 | btn.style.position = 'relative'; 387 | btn.style.fontSize = '1rem'; 388 | te.appendChild(btn); 389 | } 390 | 391 | // get series ASIN 392 | const [srasin, c] = get_series_asin(document.body); 393 | 394 | // API access 395 | const res = await access_api_params((srasin ? `COL_${srasin},` : '') + asin); 396 | if (!res?.result) return; 397 | 398 | // display jsdr information 399 | if (srasin) { 400 | const sr_jsdr = get_series_jsdr(srasin, res); 401 | if (sr_jsdr >= JSDR_CUTOFF) show_series_sale_badge(c); 402 | } 403 | const jsdr = get_jsdr(asin, res, {asin: pinfo}); 404 | if (jsdr < JSDR_CUTOFF) return; 405 | const x = document.querySelector('#leftCol'); // '#imageBlock' 406 | change_background_color(x, jsdr, 'g'); 407 | const x2 = x.querySelector('div#imageBlock .a-fixed-left-grid'); 408 | show_jsdr_badge(x2, jsdr, "0", "0"); 409 | 410 | return; 411 | } 412 | 413 | //// Wishlist Page 414 | // Ex. https://www.amazon.co.jp/hz/wishlist/ls/ 415 | async function wishlist_page() { 416 | 417 | // collect ASINs for API access and put price graph buttons 418 | const a2pinfo = {}; 419 | const asins = []; 420 | // PC page (list): ul#g-items > li 421 | // PC page (grid): ul#g-items-grid > li 422 | // SP page: ul#awl-list-items > li 423 | const elems = [...document.querySelectorAll('ul#g-items > li, ul#g-items-grid > li, ul#awl-list-items > li')]; 424 | elems.forEach(cntn => { 425 | if (cntn.querySelector('.kiseppe-pg-btn')) return; 426 | if (!/Kindle版/.test(cntn.textContent) && 427 | !/1-Clickで今すぐ買う/.test(cntn.textContent)) return; 428 | 429 | let asin = ''; 430 | let item_title = ''; 431 | const di = cntn.querySelector('div[data-csa-c-item-id]'); 432 | if (di) { // list 433 | asin = di.dataset.csaCItemId; 434 | if (!asin.startsWith("B")) return; 435 | item_title = cntn.querySelector('img[height]')?.getAttribute('alt'); 436 | a2pinfo[asin] = extract_price_and_point(cntn); 437 | put_price_graph_button(cntn, asin, item_title, a2pinfo[asin]); 438 | } else { // grid 439 | const firstLink = cntn.querySelector('a'); 440 | if (!firstLink) return; 441 | const regex = /\/dp\/(B[0-9A-Z]{9})\//; 442 | const match = firstLink.href.match(regex); 443 | if (!match) return; 444 | asin = match[1]; 445 | item_title = firstLink.title; 446 | a2pinfo[asin] = extract_price_and_point(cntn); 447 | const c = cntn.querySelector('.wl-grid-item-selectable') 448 | put_price_graph_button(c, asin, item_title, a2pinfo[asin]); 449 | } 450 | asins.push(asin); 451 | cntn.dataset.ks_asin = asin; 452 | }); 453 | if (asins.length <= 0) return; 454 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 455 | 456 | // API access 457 | const res = await access_api_params(asins_pp.join(",")); 458 | 459 | // display jsdr information 460 | Object.keys(res?.result?.books || []).forEach(asin => { 461 | const cntn = document.querySelector(`li[data-ks_asin=${asin}]`); 462 | const jsdr = get_jsdr(asin, res, a2pinfo); 463 | if (jsdr < JSDR_CUTOFF) return; 464 | let ca = cntn.querySelector('[id^=itemImage]'); 465 | if (ca) { // list 466 | show_jsdr_badge(ca, jsdr, "0", "0"); 467 | change_background_color(cntn, jsdr); 468 | } else { // grid 469 | ca = cntn.querySelector('.wl-grid-item-middle-section'); 470 | if (!ca) return; 471 | show_jsdr_badge(ca, jsdr, "1", "0"); 472 | change_background_color(ca, jsdr); 473 | } 474 | }); 475 | 476 | return; 477 | } 478 | 479 | //// Kindle Author Component 480 | // Ex. https://www.amazon.co.jp/kindle-dbs/entity/author/B004L41ULY 481 | // => https://www.amazon.co.jp/stores/.../author/B004L41ULY 482 | async function kindle_author_component() { 483 | 484 | // collect ASINs for API access and put price graph buttons 485 | const a2pinfo = {}; 486 | const asins = []; 487 | // li[data-csa-c-item-type=asin] : グリッド表示 488 | // div[data-csa-c-item-type=asin] : 大カードリスト表示 489 | const elems = [...document.querySelectorAll('[data-csa-c-item-type=asin]')]; 490 | elems.forEach(cntn => { 491 | if (cntn.querySelector('.kiseppe-pg-btn')) return; 492 | const c = cntn.querySelector('a[href*="dp/B"]'); 493 | if (!c) return; // not Kindle book 494 | const asin = get_asin_in_href(c); 495 | asins.push(asin); 496 | cntn.dataset.ks_asin = asin; 497 | const item_title = c.getAttribute('aria-label') || c.getAttribute('title'); 498 | a2pinfo[asin] = extract_price_and_point(cntn); 499 | put_price_graph_button(cntn, asin, item_title, a2pinfo[asin]); 500 | }); 501 | if (asins.length <= 0) return; 502 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 503 | 504 | // API access 505 | const res = await access_api_params(asins_pp.join(",")); 506 | 507 | // display jsdr information 508 | Object.keys(res?.result?.books || []).forEach(asin => { 509 | const cntn = document.querySelector(`[data-ks_asin=${asin}]`); 510 | const jsdr = get_jsdr(asin, res, a2pinfo); 511 | if (jsdr < JSDR_CUTOFF) return; 512 | //const ca = cntn.querySelector('a[aria-label]'); 513 | show_jsdr_badge(cntn, jsdr, "0", "0"); 514 | change_background_color(cntn, jsdr); 515 | }); 516 | 517 | return; 518 | } 519 | 520 | //// Kindle Grid30 Page 521 | // グリッド表示 522 | // Ex. https://www.amazon.co.jp/kindle-dbs/browse?metadata=cardAppType&storeType=ebooks&sourceType=recs&widgetId=unified-ebooks-storefront-default_KindleUnlimitedCrossCategoryStrategyEbookSources 523 | async function kindle_grid30_page() { 524 | const qs_grid30 = 'div[id="browse-views-area"] div[class*="browse-clickable-item"]'; 525 | 526 | // get all ASINs 527 | const a2pinfo = {}; 528 | const asins = []; 529 | const elems = [...document.querySelectorAll(qs_grid30)]; 530 | elems.forEach(e => { 531 | const le = e.querySelector('a[href^="/gp/product/B"][aria-label]'); 532 | const asin = get_asin_in_href(le); 533 | if (!asin) return; 534 | asins.push(asin); 535 | const item_title = le.getAttribute('aria-label'); 536 | a2pinfo[asin] = extract_price_and_point(e); 537 | put_price_graph_button(e, asin, item_title, a2pinfo[asin]); 538 | }); 539 | if (asins.length <= 0) return; 540 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 541 | 542 | // API access 543 | const res = await access_api_params(asins_pp.join(",")); 544 | 545 | // display jsdr information 546 | Object.keys(res?.result?.books || []).forEach(asin => { 547 | const e = document.querySelector( 548 | `${qs_grid30} a[href^="/gp/product/${asin}"][aria-label]` 549 | ); 550 | const cntn = e.closest('div[class*="browse-clickable-item"]'); 551 | const jsdr = get_jsdr(asin, res, a2pinfo); 552 | if (jsdr < JSDR_CUTOFF) return; 553 | show_jsdr_badge(cntn, jsdr, "0", "0"); 554 | change_background_color(cntn, jsdr, 'g'); 555 | }); 556 | 557 | return; 558 | } 559 | 560 | //// Kindle Octopus Component 561 | // 特設ページなどの下の方に12個固定で表示されるやつ 562 | // Ex. https://www.amazon.co.jp/b?node=22083216051 563 | async function kindle_octopus_component() { 564 | let e = document.querySelector('[data-card-metrics-id*="octopus-search-result-card_"'); 565 | 566 | // get all ASINs 567 | const a2pinfo = {}; 568 | const aslist = []; 569 | const calist = []; 570 | const elems = [...e.querySelectorAll("a[href] > h2")]; 571 | elems.forEach(e => { 572 | const asin = get_asin_in_href(e.parentNode); 573 | if (!asin) return; 574 | const cntn = e.closest('div[class*="s-card-container"]'); 575 | if (cntn.querySelector('.kiseppe-pg-btn')) return; 576 | const [srasin, seri] = get_series_asin(cntn); 577 | if (srasin) calist.push(...['COL_' + srasin, asin]); 578 | else aslist.push(asin); 579 | a2pinfo[asin] = extract_price_and_point(cntn); 580 | put_price_graph_button(cntn, asin, e.textContent, a2pinfo[asin]); 581 | }); 582 | const asins = [aslist, calist].flat(); 583 | if (asins.length <= 0) return; 584 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 585 | 586 | // API access 587 | const res = await access_api_params(asins_pp.join(",")); 588 | 589 | // display jsdr information 590 | Object.keys(res?.result?.books || []).forEach(asin => { 591 | const e = document.querySelector(`a[href*="/dp/${asin}"] > h2`); 592 | if (!e) return; 593 | //console_log('kiseppe: octopus-component', asin); 594 | const cntn = e.closest('div[class*="s-card-container"]'); 595 | const [srasin, seri] = get_series_asin(cntn); 596 | const sr_jsdr = get_series_jsdr(srasin, res); 597 | if (sr_jsdr >= JSDR_CUTOFF) show_series_sale_badge(seri); 598 | const jsdr = get_jsdr(asin, res, a2pinfo); 599 | if (jsdr < JSDR_CUTOFF) return; 600 | show_jsdr_badge(cntn, jsdr, "0", "0"); 601 | change_background_color(cntn, jsdr); 602 | }); 603 | 604 | return; 605 | } 606 | 607 | //// Kindle Grid12 Component 608 | // 「セール&キャンペーン」ページなどで表示される検索結果表示 609 | // Ex. https://www.amazon.co.jp/hko/deals/?_encoding=UTF8 610 | async function kindle_grid12_component() { 611 | let e = document.querySelector('div[id=search-results]').parentNode; 612 | 613 | // get all ASINs 614 | const a2pinfo = {}; 615 | const asins = []; 616 | const elems = [...document.querySelectorAll('div[class*="asin-container"] a[href]')]; 617 | elems.forEach(e => { 618 | const asin = get_asin_in_href(e); 619 | if (!asin) return; 620 | asins.push(asin); 621 | const c = e.closest('div[class*="asin-container"]'); 622 | if (c.querySelector('.kiseppe-pg-btn')) return; 623 | const item_title = e.querySelector('img[alt]').getAttribute('alt'); 624 | a2pinfo[asin] = extract_price_and_point(c); 625 | put_price_graph_button(c, asin, item_title, a2pinfo[asin]); 626 | }); 627 | if (asins.length <= 0) return; 628 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 629 | 630 | // API access 631 | const res = await access_api_params(asins_pp.join(",")); 632 | 633 | // display jsdr information 634 | Object.keys(res?.result?.books || []).forEach(asin => { 635 | if ((e = document.querySelector( 636 | `div[class*="asin-container"] a[href*="/dp/${asin}"]` 637 | )) !== null) { 638 | const c = e.closest('div[class*="asin-container"]'); 639 | const jsdr = get_jsdr(asin, res, a2pinfo); 640 | if (jsdr < JSDR_CUTOFF) return; 641 | show_jsdr_badge(c, jsdr, "0", "0"); 642 | change_background_color(c, jsdr, 'g'); 643 | } 644 | }); 645 | 646 | return; 647 | } 648 | 649 | //// Kindle Series Page 650 | // ("collection" means "series") 651 | async function kindle_series_page() { 652 | 653 | // get series ASIN 654 | let e = document.querySelector('[data-asin][data-entity-type=collection]'); 655 | let srasin = e?.dataset?.asin; 656 | if (!srasin) { 657 | e = document.querySelector('[data-collection-asin*="B"]'); 658 | srasin = e?.dataset?.collectionAsin; 659 | } 660 | if (!srasin) { 661 | e = document.querySelector('[data-parent-asins*="B"]'); 662 | const srasins = e?.dataset?.parentAsins; 663 | if (srasins) srasin = srasins.split(",")[0]; 664 | } 665 | if (!srasin) return; 666 | //console_log('series asin:', srasin); 667 | 668 | // get ASINs 669 | const a2pinfo = {}; 670 | const aset = new Set(); 671 | // Ex. data-ajax-url="...B074V5W2R7,B074V3V9W5,B074V5W5GT" 672 | let elems = [...document.querySelectorAll('[data-ajax-url*="B"]')]; 673 | elems.forEach(e => { 674 | const r = e.getAttribute('data-ajax-url').match(/(B[0-9A-Z]{9})/g); 675 | r.forEach(s => aset.add(s)); 676 | }); 677 | // all items (in this series) displayed on this page 678 | elems = [...document.querySelectorAll('div[id^="series-childAsin-item_"]')]; 679 | elems.forEach(e => { 680 | if (e.querySelector('.kiseppe-pg-btn')) return; 681 | const co = e.querySelector(`a[class*="itemImageLink"][role="img"]`); 682 | const asin = get_asin_in_href(co); 683 | if (!asin) return; 684 | aset.add(asin); 685 | const item_title = co.getAttribute('title'); 686 | a2pinfo[asin] = extract_price_and_point(e); 687 | put_price_graph_button(e, asin, item_title, a2pinfo[asin]); 688 | }); 689 | const asins = Array.from(aset); 690 | if (asins.length <= 0) return; 691 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 692 | 693 | // API access 694 | const res = await access_api_params(`COL_${srasin},` + asins_pp.join(",")); 695 | 696 | // display jsdr information 697 | const sr_jsdr = get_series_jsdr(srasin, res); 698 | if (sr_jsdr >= JSDR_CUTOFF) { 699 | const c = document.getElementById('collection-masthead__title') ?? 700 | document.getElementById('collection-title'); 701 | if (c) show_series_sale_badge(c.parentNode); 702 | } 703 | Object.keys(res?.result?.books || []).forEach(asin => { 704 | const co = document.querySelector(`a[href*="${asin}"][role="img"]`); 705 | if (!co) return; 706 | const cntn = co.closest('div[id^="series-childAsin-item_"]'); 707 | const jsdr = get_jsdr(asin, res, a2pinfo); 708 | if (jsdr < JSDR_CUTOFF) return; 709 | show_jsdr_badge(co, jsdr, "0", "-4px"); 710 | change_background_color(cntn, jsdr); 711 | }); 712 | 713 | return; 714 | } 715 | 716 | //// Kindle Ranking Page 717 | // Ex. https://www.amazon.co.jp/gp/new-releases/digital-text/ 718 | // Ex. https://www.amazon.co.jp/gp/bestsellers/digital-text/ 719 | // Ex. https://www.amazon.co.jp/gp/movers-and-shakers/digital-text/ 720 | async function kindle_ranking_page() { 721 | 722 | // collect ASINs for API access and put price graph buttons 723 | const a2pinfo = {}; 724 | const asins = []; 725 | const elems = [...document.querySelectorAll('div[id^="p13n-asin-index-"]')]; 726 | elems.forEach(e => { 727 | if (e.querySelector('.kiseppe-pg-btn')) return; 728 | const co = e.querySelector('div[class^="p13n-sc-un"]'); 729 | const asin = co.id; 730 | const item_title = co.querySelector('img').getAttribute('alt'); 731 | asins.push(asin); 732 | a2pinfo[asin] = extract_price_and_point(e); 733 | put_price_graph_button(e, asin, item_title, a2pinfo[asin]); 734 | }); 735 | if (asins.length <= 0) return; 736 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 737 | 738 | // API access 739 | const res = await access_api_params(asins_pp.join(",")); 740 | 741 | // display jsdr information 742 | Object.keys(res?.result?.books || []).forEach(asin => { 743 | const co = document.querySelector(`div[id="${asin}"]`); 744 | const cntn = co.closest('div[data-a-card-type]'); 745 | const jsdr = get_jsdr(asin, res, a2pinfo); 746 | if (jsdr < JSDR_CUTOFF) return; 747 | show_jsdr_badge(co, jsdr, "0", "0"); 748 | change_background_color(cntn, jsdr); 749 | }); 750 | 751 | return; 752 | } 753 | 754 | //// Kindle Search Page 755 | // Ex. https://www.amazon.co.jp/s?rh=n%3A2410280051&fs=true 756 | async function kindle_search_page() { 757 | 758 | console_log("wait a few seconds"); 759 | await sleep(1600); 760 | console_log("ok, go!"); 761 | 762 | // collect ASINs for API access and put price graph buttons 763 | const a2pinfo = {}; 764 | const aslist = []; 765 | const calist = []; 766 | const elems = [...document.querySelectorAll( 767 | 'div[data-asin][data-component-type="s-search-result"]' 768 | )]; 769 | //document.querySelectorAll( 770 | // 'div[data-asin][data-component-type="s-search-result"]' 771 | //).forEach(e => { 772 | elems.forEach(e => { 773 | if (e.querySelector('.kiseppe-pg-btn')) return; 774 | const asin = e.dataset.asin; 775 | if (!asin) return; 776 | 777 | const [srasin, seri] = get_series_asin(e); 778 | if (srasin) calist.push(...['COL_' + srasin, asin]); 779 | else aslist.push(asin); 780 | 781 | const item_title = e.querySelector('h2').textContent; 782 | const c = e.querySelector('div[cel_widget_id]'); 783 | a2pinfo[asin] = extract_price_and_point(e); 784 | put_price_graph_button(c, asin, item_title, a2pinfo[asin]); 785 | }); 786 | const asins = [aslist, calist].flat(); 787 | if (asins.length <= 0) return; 788 | const asins_pp = add_priceinfo_to_asinlist(asins, a2pinfo); 789 | 790 | // API access 791 | const res = await access_api_params(asins_pp.join(",")); 792 | 793 | // display jsdr information 794 | Object.keys(res?.result?.books || []).forEach(asin => { 795 | const cntn = document.querySelector(`div[data-asin="${asin}"]`); 796 | const [srasin, seri] = get_series_asin(cntn); 797 | const sr_jsdr = get_series_jsdr(srasin, res); 798 | if (sr_jsdr >= JSDR_CUTOFF) show_series_sale_badge(seri); 799 | const jsdr = get_jsdr(asin, res, a2pinfo); 800 | if (jsdr < JSDR_CUTOFF) return; 801 | let x = cntn.querySelector('img').closest('.puisg-col-inner'); 802 | if (!x) x = cntn.querySelector('img').closest('.sg-col-inner'); 803 | show_jsdr_badge(x, jsdr, "4px", "0"); 804 | const c = cntn.querySelector('div[cel_widget_id]'); 805 | change_background_color(c, jsdr); 806 | const b = cntn.querySelector('div[class*="-badge-container"]'); 807 | if (b) change_background_color(b, 0); 808 | }); 809 | 810 | return; 811 | } 812 | 813 | //// Kindle Carousel Component 814 | async function kindle_carousel_component() { 815 | 816 | const a2pinfo = {}; 817 | const asinss = []; 818 | 819 | //// manga store ranking carousel 820 | // マンガストアトップ ランキング横スクロール表示 821 | // Ex. https://www.amazon.co.jp/kindle-dbs/manga-store/ 822 | const qs_rk = 'div[class^="_manga-genre-ranking-card_style_grid-container"]'; 823 | if (document.querySelector(qs_rk)) { 824 | console_log('kiseppe: > manga-store ranking area'); 825 | const asins = []; 826 | const ca = document.querySelector(qs_rk); 827 | const elems = [...ca.querySelectorAll( 828 | 'a[href*="/dp/B"] img[class*="_manga-genre-ranking-card_retail-item-style_book-cover"]' 829 | )]; 830 | elems.forEach(e => { 831 | const cntn = e.closest('div[id^="grid-item_"]'); 832 | if (cntn.querySelector('.kiseppe-pg-btn')) 833 | return; // button already exists 834 | if (cntn.querySelector('img[alt="likes icon"]')) 835 | return; // manga-store mateba-muryou's "likes" icon 836 | const asin = get_asin_in_href(e.closest('a')); 837 | const item_title = e.getAttribute('alt') 838 | const pinfo = extract_price_and_point(cntn); 839 | a2pinfo[asin] = pinfo; 840 | asins.push(asin); 841 | cntn.dataset.ks_asin = asin; 842 | put_price_graph_button(cntn, asin, item_title, pinfo); 843 | }); 844 | if (asins.length > 0) asinss.push(asins); 845 | } 846 | 847 | //// general carousel 848 | // カテゴリTOP: div[class*="octopus-pc-card-content"] 849 | // ASIN page: %2Fdp%2FB00JGI56B0%2F 850 | // 横並びだけどスクロールしないやつ 851 | // Ex. https://www.amazon.co.jp/b?node=2292699051 852 | const qs_carousel = 853 | 'div[class*="a-carousel-row-inner"], ' + 854 | 'div[class*="octopus-pc-card-content"]'; 855 | const elems1 = [...document.querySelectorAll(qs_carousel)]; 856 | elems1.forEach(ca => { 857 | if (ca.querySelector('img[alt="likes icon"]')) 858 | return; // manga-store mateba-muryou's "likes" icon 859 | if (ca.querySelector('span[class*="collection-type"]')) 860 | return; // manga-store collection ASIN 861 | if (ca.querySelector('[class*="a-icon-prime"]')) 862 | return; // ASIN page non-kindle-books 863 | console_log('kiseppe: > general carousel'); 864 | const asins = []; 865 | const elems2 = [...ca.querySelectorAll( 866 | 'li a[href*="/dp/B"] img[alt], ' + 867 | 'li a[href^="/gp/product/B"] img[alt], ' + 868 | 'li a[href*="%2Fdp%2FB"] img[alt]' 869 | )]; 870 | elems2.forEach(e => { 871 | if (/image-ku/.test(e.getAttribute('src'))) return; 872 | let cntn = e.closest('div[class^="_manga-store-shoveler_style_item-row-"]'); // two rows 873 | if (!cntn) cntn = e.closest('li'); 874 | if (cntn.querySelector('.kiseppe-pg-btn')) 875 | return; // button already exists 876 | if (/banner-/.test(cntn.getAttribute("class"))) 877 | return; // manga-store big banner 878 | if (cntn.querySelector('[data-endtime]')) 879 | return; // with timer 880 | 881 | const asin = get_asin_in_href(e.closest('a')); 882 | const item_title = e.getAttribute('alt') 883 | const pinfo = extract_price_and_point(cntn); 884 | a2pinfo[asin] = pinfo; 885 | asins.push(asin); 886 | cntn.dataset.ks_asin = asin; 887 | put_price_graph_button(cntn, asin, item_title, pinfo); 888 | }); 889 | if (asins.length > 0) asinss.push(asins); 890 | }); 891 | 892 | for (const i in asinss) { 893 | const asins = asinss[i]; 894 | 895 | // API access 896 | const res = await access_api_params(asins.join(",")); 897 | // display jsdr information 898 | Object.keys(res?.result?.books || []).forEach(asin => { 899 | const jsdr = get_jsdr(asin, res, a2pinfo); 900 | if (jsdr < JSDR_CUTOFF) return; 901 | const elems = [...document.querySelectorAll(`[data-ks_asin="${asin}"]`)]; 902 | elems.forEach(cntn => { 903 | let c = cntn.querySelector('.octopus-pc-asin-info-section'); 904 | if (c) { 905 | show_jsdr_badge(cntn, jsdr, "2px", "0"); 906 | change_background_color(c, jsdr); 907 | return; 908 | } 909 | show_jsdr_badge(cntn, jsdr, "0", "0"); 910 | change_background_color(cntn, jsdr, 'g'); 911 | }); 912 | }); 913 | 914 | await sleep(700); 915 | } 916 | 917 | return; 918 | } 919 | 920 | //// Kindle shadowRoot Carousel Component 921 | async function kindle_shadowroot_carousel_component() { 922 | 923 | // Kindle Store Front Page 924 | // Ex. https://www.amazon.co.jp/kindle-dbs/storefront 925 | if (/kindle-dbs\/storefront/.test(location.href)) { 926 | console_log('kiseppe: > storefront carousel'); 927 | 928 | console_log("wait a few seconds"); 929 | await sleep(1000); 930 | console_log("ok, go!"); 931 | 932 | //const qs_cs = 'div[id^="unified-ebooks-storefront-default_"]'; 933 | const qs_cs = 'div.celwidget'; 934 | const a2pinfo = {}; 935 | const asinss = []; 936 | const elems1 = [...document.querySelectorAll(qs_cs)]; 937 | elems1.forEach(ca => { 938 | //console.log('ca',ca); 939 | const e = ca.querySelector('bds-render-context-provider'); 940 | if (!e) return; 941 | const slot = e.shadowRoot.querySelector('slot'); 942 | const assignedNodes = slot.assignedNodes({flatten: true}); 943 | //console.log('assignedNodes',assignedNodes); 944 | const asins = []; 945 | const elems2 = [...assignedNodes[0].querySelectorAll('bds-carousel-item')]; 946 | elems2.forEach(li => { 947 | if (li.querySelector('.kiseppe-pg-btn')) 948 | return; // button already exists 949 | //console.log(li); 950 | const e2 = li.querySelector('bds-unified-book-faceout'); 951 | if (!e2) return;//{console.log('no e2',li); return;} 952 | const bi = e2.shadowRoot.querySelector('.unified-book-faceout'); 953 | if (!bi) return;//{console.log('no bi',e2); return;} 954 | const at = bi.querySelector('a'); 955 | if (!at) return;//{console.log('no at',bi); return;} 956 | const asin = get_asin_in_href(at); 957 | const item_title = at.querySelector('bds-book-cover-image').getAttribute('coverimagealttext'); 958 | const pinfo = {'price':undefined, 'point':undefined}; 959 | a2pinfo[asin] = pinfo; 960 | //console.log(bi, asin, item_title, pinfo); 961 | asins.push(asin); 962 | li.dataset.ks_asin = asin; 963 | 964 | put_price_graph_button(li, asin, item_title, pinfo); 965 | }); 966 | if (asins.length > 0) asinss.push(asins); 967 | }); 968 | 969 | for (const i in asinss) { 970 | const asins = asinss[i]; 971 | 972 | // API access 973 | const res = await access_api_params(asins.join(",")); 974 | 975 | // display jsdr information 976 | Object.keys(res?.result?.books || []).forEach(asin => { 977 | const jsdr = get_jsdr(asin, res, a2pinfo); 978 | if (jsdr < JSDR_CUTOFF) return; 979 | const elems = [...document.querySelectorAll(`[data-ks_asin="${asin}"]`)]; 980 | elems.forEach(cntn => { 981 | show_jsdr_badge(cntn, jsdr, "0", "0"); 982 | change_background_color(cntn, jsdr); 983 | }); 984 | }); 985 | 986 | await sleep(700); 987 | } 988 | 989 | return; 990 | } 991 | } 992 | 993 | 994 | //////////////////////////////////////////////////////////////// 995 | //// Miscellaneous Small Functions 996 | 997 | const sleep = ms => new Promise(res => setTimeout(res, ms)); 998 | 999 | const get_asin_in_href = (e) => 1000 | (e?.getAttribute('href')?.match(/B[0-9A-Z]{9}/) || [])[0] ?? ''; 1001 | 1002 | const get_series_asin = (e) => { 1003 | const c = e?.querySelector("a[href*='binding=kindle_edition']"); 1004 | const srasin = get_asin_in_href(c); 1005 | return [srasin, c]; 1006 | } 1007 | 1008 | const get_series_jsdr = (srasin, apires) => 1009 | Number(apires?.result?.series?.[srasin] ?? ''); 1010 | 1011 | function get_jsdr(asin, apires, current) { 1012 | const [api, now] = [apires?.result?.books?.[asin], current?.[asin]] 1013 | if (!api) return 0; 1014 | if (!now || now.price === void 0) return Number(api.jsdr); 1015 | 1016 | const max_price = Number(api.max_price); 1017 | if (max_price == 0) return 0; 1018 | 1019 | const calc_jsdr = (pr, po, mx) => { 1020 | let v = Math.ceil((mx - (pr - po)) / mx * 100); 1021 | if (v == 100 & 0 < (pr - po)) v = 99; 1022 | return (0 <= v & v <= 100) ? v : 0; 1023 | }; 1024 | if (now.point === void 0) { // case: point not displayed (ex. ku) 1025 | if (Number(now.price) < Number(api.latest_price)) 1026 | return calc_jsdr(Number(now.price), 0, max_price); 1027 | } else { // case: point displayed 1028 | if (now.price != api.latest_price || now.point != api.latest_point) 1029 | return calc_jsdr(Number(now.price), Number(now.point), max_price); 1030 | } 1031 | return Number(api.jsdr); 1032 | } 1033 | 1034 | function extract_price_and_point(e) { 1035 | console_groupCollapsed('extract_price_and_point'); 1036 | console.assert(e, 'e'); 1037 | let price; 1038 | let point; 1039 | 1040 | try { 1041 | //const elms = [...e.querySelectorAll('.selected')]; 1042 | //const html = (elms.length ? elms : [e]).map(n => n.innerHTML).join(' '); 1043 | //const elm = e.querySelector('.selected'); 1044 | //const html = elm?.innerHTML || ''; 1045 | const html = e?.innerHTML || ''; 1046 | const s = html.replace(/(