├── 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 |
81 |
82 |
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 |
81 |
82 |
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(/(