LINEスタンプのタグ編集効率化ブックマークレット番外編【まとめて登録式】スーパー改訂版
毎度rorosukeです。
LINEスタンプ(特にLINEアニメスタンプ)のタグ編集が面倒だということで、これを自動化するための方策を考えていました。そして先日、Chameさんのアイデアを採用させていただきまして、「まとめて登録式ブックマークレット」を公開しました。
詳しい経緯につきましては、こちらをご覧ください。
今回、「まとめて登録式ブックマークレット」の改訂版を公開します。いわゆるアップデート版で、動作を確実なものにしたり、新しい機能を追加していますのでご期待ください。
そもそもどんなことをするツールなのか
あらためて説明すると、LINEスタンプを登録するときに面倒なタグ編集を自動的に行うためのブックマークレットです。ブックマークレットとは、ブックマークとして登録・実行できるプログラムのことで、ブラウザのアドレスバーやブックマークバーからワンクリックで実行できます。
このツールを使うと、スタンプ1枚1枚に手作業でタグを設定していく作業を、テキストを貼り付けるだけで全スタンプ分まとめて自動処理できます。スタンプ枚数が多いほど、劇的に時間を短縮できます。
今回の改訂内容
今回の改訂はかなり大きく、動作の安定性向上と新機能追加の両面で進化しています。
動作の安定化
以前のバージョンでは、環境によってタグが9個に満たないまま次のスタンプへ進んでしまうことがありました。これはタグのチェックボックスをクリックした後、固定の待機時間(45ミリ秒)で次へ進んでいたためです。
今回の改訂では、チェックボックスに実際にチェックが入ったことを確認してから次へ進む「状態確認ベース」の処理に変更しました。
Chameさんが実際に動作を改善されたコードをご報告くださり、その方式を採用しています。
この仕組みをページ遷移にも応用し、同様に「次へ」ボタンが一時的に無効状態になっている間はボタンが有効になるまで待機してからクリックするよう改善しました。全体的な安定度も格段にアップしました。
確認待ち時間の追加
タグを保存した後、設定内容を目視で確認できるよう、待機時間を設けました。デフォルトは3秒です。確認せず速く処理したい場合は短くできますし、じっくり確認したい場合は長くできます。この待機時間はサーバー側の負担軽減にもつながります。
不足分追加モードの追加
従来のバージョンは「全入れ替え」(既存タグを一度すべて削除してから9個登録する)のみでしたが、今回「不足分追加モード」が加わりました。
すでに設定したタグを消したくないとき、それを残したまま、空いているスロット分だけ新しいタグを追加する機能です。たとえば、すでに3個登録済みであれば、残り6個分だけ追加します。タグを途中から追加したいときや、一部だけ手直ししたいときに便利です。デフォルトはこちらの「不足分追加モード」になっていますので、すでに自分でタグ登録している場合、それを誤って消してしまうミスが起きにくくなっています。
処理中のUI改善
処理中はメインダイアログが右下の小さなステータスバーに縮小し、スタンプ画像とタグ設定UIが見えるようになりました。これにより、タグ編集中の画像も確認しやすくなりました。
処理中にいつでも処理を中断できる「処理を中断する」ボタンもミニバー内に設置しました。一度スタートしても、いつでも止めることができます。
入力精度の向上
LINEのタグリストに、LINEタグとして存在しないワードが含まれていた場合、残りのワードで補填して9個になるよう改善しました。また、入力データに同じタグが重複して含まれていた場合も自動で除去します。
用意するタグリスト
このツールを使うには、スタンプ番号とタグのリストをテキスト形式で用意する必要があります。形式は次のとおりです。
01,タグA,タグB,タグC,タグD,タグE,タグF,タグG,タグH,タグI
02,タグX,タグY,タグZ,タグW,タグV,タグU,タグT,タグS,タグR
1行が1スタンプに対応します
先頭がスタンプ番号(01, 02 … のような形式)
カンマ区切りでタグを列挙します
タグは最大14個まで指定できます(無効なタグを除いた中から先頭9個が登録されます)
LINEのタグリストに存在しないワードは自動的に除外されます
ご注意: LINEのタグリストに存在するワードのみ有効です。存在しないワードは自動除外されますが、AIが架空のタグを提案することもあるため、14個指定することで補填が効くようにしています。
実際のタグリストは次のような感じです。
07,え?,驚く,動揺,びっくり,!?,えー,あらま,なんと,まじか,衝撃,どうしよう,!!,そんな,慌てる
08,恥ずかしい,照れる,えへへ,てへ,キュート,かわいい,萌える,嬉しい,!!,ときめく,ハート,謙遜,和む
09,怒る,ぷんぷん,不機嫌,腹が立つ,いらいら,!!,こらー,不満,我慢,ピキピキ,カチーン,むかつく,限界,本気
10,ショック,がっかり,しんみり,どんより,ちーん,喪失,悲しい,寂しい,憂鬱,絶望,限界,落ちこむ,とほほ,!!
01,お大事に,心配,無理しないで,大丈夫,慰める,しんみり,悲しい,申し訳ない,お願いします,どうぞ,不安,ゆっくり,!!,挨拶
02,わかった,了解,承知,またね,バイバイ,はい,楽しみ,かわいい,にっこり,嬉しい,仕事中,まったり,!!,待ってる
03,おやすみ,バイバイ,さようなら,またね,寝る,眠い,にっこり,挨拶,じゃあね,ゆっくり,まったり,かわいい,!!,お疲れ様
04,休憩,どうぞ,のんびり,まったり,お疲れ様,ゆっくり,和む,どうぞ,どうぞ,いただきます,ごはん,美味しい,!!,安心,無理しないで
05,任せろ,よし,頼む,わかった,かわいい,にっこり,最高,嬉しい,!!,応援,ファイト,大丈夫,よしよし,甘える
06,ごめんなさい,申し訳ない,謝る,すみません,反省,しくしく,涙,悲しい,泣く,ぐすん,ショック,どんより,!!,困る
18,わからない,どうしよう,困る,不安,悲しい,しょんぼり,うーん,心配,まじか,何故,もやもや,落ちこむ,涙,しんみり
19,だめ,NG,怒る,ぷんぷん,カチーン,ピキピキ,こらー,腹が立つ,不機嫌,むかつく,いらいら,許さない,NO,はぁ?
20,違う,ツッコミ,真面目,しっかり,ちょっと待った,!?,こらー,NO,却下,断る,本気,だめ,NG,怒る
11,了解,承知,任せろ,任せた,問題ない,気合,よし,OK,YES,元気,一番,どや,しっかり,本気
12,仕事中,勉強中,考える,真面目,しっかり,忙しい,本気,冷静,ふむふむ,なるほど,一生懸命,悩む,プレッシャー,頑張る
13,おつかれさま,退勤,ぺこり,すみません,申し訳ない,バイバイ,じゃあね,またね,感謝,ありがとう,安心,疲れた,ぐったり,今帰る
14,休憩,ごはん,食べる,もぐもぐ,パクパク,おいしい,満足,満腹,のんびり,まったり,幸せ,わーい,空腹,腹ペコ
15,なるほど,ふむふむ,考える,うーん,そっか,そうなの?,わかるわかる,本当,?,ほほう,キョトン,じーっ,チラッ,冷静
16,最高,OK,いいね,素晴らしい,やった,わーい,イエイ,嬉しい,幸せ,満足,きらきら,拍手,すごい,一番
17,許さない,怒る,ぷんぷん,カチーン,ピキピキ,不機嫌,腹が立つ,NO,断る,こらー,だめ,NG,本気,冷静※番号順に並んでないのは、処理前にソートする仕様だからです。もちろん順番通りに並べても何ら問題ありません。
タグリストはAIに生成してもらうのがおすすめです。画像を見せて「このスタンプに合いそうなLINEスタンプのタグを14個提案してください」のように依頼するだけで、すぐに候補を出してもらえます。
タグリストを効率的に作る方法は、Chameさんの次の記事が参考になります。Gemというツールを使って作る方法を紹介しています。
また、次にも異なるアプローチでタグリストを作る方法(プロンプトコピー用ブックマークレット)を紹介していますので参考にしてください。
準備(インストール方法)
通常ブックマークにはURLが登録され、選択するとホームページが表示されます。しかし、ここにはプログラムを登録することもでき、選択するとプログラム(ブックマークレット)を実行することができます。
つまり、ブックマークレットのインストールとは、ブックマークとしてURLを設定するのとまったく同じです。
① コードをコピーする
本記事の最後で、ブックマークレットを公開しています。
インストールするには、コードをすべてコピーします。1行プログラムなので、短く見えますが、実際にはかなり巨大なコードですので、ご注意ください。
② ブックマークとして登録する
ブラウザのブックマークバーを右クリック →「ページを追加」または「ブックマークを追加」を選択します。
名前: 「LINEタグ一括登録」など、わかりやすい名前をつけてください
URL: コピーしたコードをそのまま貼り付けます

Chromeの場合、アドレスバーに chrome://bookmarks と入力してブックマークマネージャーを開き、右クリックから新しいブックマークを追加する方法でも登録できます。
③ 実行できることを確認する
LINEクリエイターズマーケット(https://creator.line.me/)にアクセスし、スタンプの編集ページを開いた状態でブックマークをクリックしてダイアログが表示されれば準備完了です。

ブックマークレットの使い方
① スタンプタグ編集ページを開く
LINEクリエイターズマーケットにログインし、登録したいスタンプの管理ページを開きます。タグ編集ページ(スタンプ画像一覧とタグが設定できるページ)を表示した状態にしてください。

② ブックマークレットを実行する
ブックマークバーに登録したブックマークレットをクリックします。ダイアログが表示されます。

③ モードを選択する
ダイアログ上部のトグルボタンでモードを切り替えます。
不足分追加(デフォルト):既存タグを残しつつ、空きスロット分を追加
全入れ替え:既存タグをすべて削除してから9個を新規登録

④ データを貼り付ける
用意したテキストデータをテキストエリアに貼り付けます。

⑤ 確認待ち時間を設定する
デフォルトは3秒です。登録後の結果を確認しながら進めたい場合はそのまま、速く処理したい場合は短くしてください。
⑥ 「一括登録開始」ボタンをクリックする
ボタンを押すと、メインダイアログが右下の小さなステータスバーに変わり、処理が始まります。スタンプ画像とタグ設定UIが見える状態になるので、タグが実際に設定される様子をリアルタイムで確認できます。
処理中に止めたい場合は、右下のミニバー内「処理を中断する」ボタンをクリックしてください。現在のスタンプの処理が終わった後に停止します。

処理内容の表示と、中断用のボタンがある
⑦ 完了後に結果を確認する
すべての処理が終わるとメインダイアログが戻り、処理結果が表示されます。エラーがあった場合は一覧で確認できます。

コード公開
こちらをコピペするか、下のファイルをダウンロードしてください。どちらも同じ内容です。
javascript:(function(){ var WAIT = function(ms){ return new Promise(function(r){ setTimeout(r, ms); }); }; var $ = function(id){ return document.getElementById(id); }; var overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.55);z-index:10000;display:flex;justify-content:center;align-items:center;font-family:sans-serif;'; var box = document.createElement('div'); box.id = 'bk-box'; box.style.cssText = 'background:#fff;border-radius:12px;padding:28px;max-width:640px;width:92%;box-shadow:0 12px 40px rgba(0,0,0,0.35);max-height:92vh;overflow-y:auto;'; box.innerHTML = '' + '<h2 style="margin:0 0 10px;color:#333;font-size:17px;">📋 一括タグ登録</h2>' + '<p style="color:#555;font-size:12px;margin:0 0 4px;">形式(1行=1スタンプ、タグ最大14個):</p>' + '<p style="font-family:monospace;background:#f3f3f3;padding:6px 10px;border-radius:4px;font-size:12px;margin:0 0 10px;">01,タグA,タグB 02,タグX,タグY</p>' + '<p style="color:#e53935;font-size:12px;margin:0 0 12px;">※ スタンプタグ編集ページで実行してください</p>' + '<div style="display:flex;align-items:stretch;margin:0 0 12px;border:1px solid #ccc;border-radius:8px;overflow:hidden;font-size:13px;">' + '<button id="bk-mode-append" style="flex:1;padding:10px 6px;border:none;cursor:pointer;font-weight:bold;background:#1565c0;color:#fff;">➕ 不足分追加</button>' + '<button id="bk-mode-replace" style="flex:1;padding:10px 6px;border:none;cursor:pointer;font-weight:bold;background:#f5f5f5;color:#999;">🔄 全入れ替え</button>' + '</div>' + '<div id="bk-mode-desc" style="font-size:11px;color:#555;background:#f9f9f9;padding:6px 10px;border-radius:4px;margin-bottom:12px;">既存タグはそのまま残し、空きスロット分だけ新規タグを追加します(最大9個)。</div>' + '<textarea id="bk-input" style="width:100%;height:200px;border:1px solid #ccc;border-radius:6px;padding:10px;font-size:12px;font-family:monospace;box-sizing:border-box;resize:vertical;" placeholder="01,ぺこり,ありがとう,助かる 02,おめでとう,お祝い,拍手"></textarea>' + '<div style="display:flex;align-items:center;gap:10px;margin:12px 0 6px;">' + '<label style="font-size:13px;color:#333;white-space:nowrap;">⏱ タグ設定後の確認待ち時間:</label>' + '<input id="bk-wait" type="number" min="0.5" max="10" step="0.5" value="3" style="width:80px;padding:6px;border:1px solid #ccc;border-radius:5px;font-size:13px;text-align:center;">' + '<span style="font-size:13px;color:#555;">秒</span>' + '</div>' + '<div style="font-size:11px;color:#888;margin-bottom:12px;">設定後のタグを確認したい場合は1〜2秒に増やしてください</div>' + '<div id="bk-progress" style="margin:10px 0 4px;font-size:13px;color:#555;min-height:20px;"></div>' + '<div id="bk-bar-wrap" style="display:none;background:#eee;border-radius:4px;height:8px;margin-bottom:10px;">' + '<div id="bk-bar" style="background:#4caf50;height:8px;border-radius:4px;width:0%;transition:width 0.3s;"></div>' + '</div>' + '<div style="display:flex;gap:10px;">' + '<button id="bk-start" style="flex:1;background:#4caf50;color:#fff;border:none;padding:13px;border-radius:6px;cursor:pointer;font-weight:bold;font-size:14px;">▶ 一括登録開始</button>' + '<button id="bk-close" style="background:#9e9e9e;color:#fff;border:none;padding:13px 20px;border-radius:6px;cursor:pointer;font-size:13px;">✕ 閉じる</button>' + '</div>' + '<div id="bk-errors" style="display:none;margin-top:14px;padding:10px;background:#fff3f3;border:1px solid #f44336;border-radius:6px;font-size:12px;color:#c62828;"></div>'; overlay.appendChild(box); document.body.appendChild(overlay); var currentMode = 'append'; var modeDesc = { replace: '既存タグをすべて削除してから、指定した9個を登録します。', append: '既存タグはそのまま残し、空きスロット分だけ新規タグを追加します(最大9個)。' }; var updateModeUI = function(){ var isReplace = currentMode === 'replace'; $('bk-mode-replace').style.background = isReplace ? '#4caf50' : '#f5f5f5'; $('bk-mode-replace').style.color = isReplace ? '#fff' : '#999'; $('bk-mode-append').style.background = isReplace ? '#f5f5f5' : '#1565c0'; $('bk-mode-append').style.color = isReplace ? '#999' : '#fff'; $('bk-mode-desc').textContent = modeDesc[currentMode]; }; $('bk-mode-replace').onclick = function(){ currentMode = 'replace'; updateModeUI(); }; $('bk-mode-append').onclick = function(){ currentMode = 'append'; updateModeUI(); }; var setStatus = function(msg, color){ $('bk-progress').style.color = color || '#555'; $('bk-progress').textContent = msg; var mb = document.getElementById('bk-mini-status'); if(mb){ mb.style.color = color || '#ddd'; mb.textContent = msg; } }; var setBar = function(cur, total){ $('bk-bar-wrap').style.display = 'block'; $('bk-bar').style.width = (cur / total * 100) + '%'; var mb = document.getElementById('bk-mini-bar'); if(mb) mb.style.width = (cur / total * 100) + '%'; }; var cancelRequested = false; var collapseToBar = function(){ cancelRequested = false; overlay.style.background = 'rgba(0,0,0,0)'; overlay.style.pointerEvents = 'none'; box.style.display = 'none'; var mini = document.createElement('div'); mini.id = 'bk-mini'; mini.style.cssText = 'position:fixed;bottom:16px;right:16px;background:rgba(30,30,30,0.88);color:#fff;border-radius:10px;padding:10px 14px;min-width:240px;max-width:340px;z-index:10001;box-shadow:0 4px 16px rgba(0,0,0,0.4);font-family:sans-serif;font-size:12px;pointer-events:auto;'; mini.innerHTML = '<div style="font-size:11px;color:#aaa;margin-bottom:4px;">📋 一括タグ登録 実行中</div>' + '<div id="bk-mini-status" style="color:#ddd;margin-bottom:6px;min-height:16px;"></div>' + '<div style="background:rgba(255,255,255,0.2);border-radius:3px;height:4px;margin-bottom:8px;">' + '<div id="bk-mini-bar" style="background:#4caf50;height:4px;border-radius:3px;width:0%;transition:width 0.3s;"></div>' + '</div>' + '<button id="bk-mini-cancel" style="width:100%;background:#c62828;color:#fff;border:none;padding:6px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:bold;">⏹ 処理を中断する</button>'; document.body.appendChild(mini); document.getElementById('bk-mini-cancel').onclick = function(){ cancelRequested = true; this.textContent = '中断処理中...'; this.disabled = true; this.style.background = '#888'; }; }; var expandToFull = function(){ var mini = document.getElementById('bk-mini'); if(mini) document.body.removeChild(mini); overlay.style.background = 'rgba(0,0,0,0.55)'; overlay.style.pointerEvents = ''; box.style.display = ''; }; $('bk-close').onclick = function(){ if(document.body.contains(overlay)) document.body.removeChild(overlay); }; overlay.onclick = function(e){ if(e.target === overlay && document.body.contains(overlay)) document.body.removeChild(overlay); }; box.onclick = function(e){ e.stopPropagation(); }; var getCurrentNum = function(){ var span = document.querySelector('button.btn-dropdown .btn-dropdown-text'); if(!span) return null; var n = parseInt(span.textContent.trim(), 10); return isNaN(n) ? null : n; }; var pressBtn = function(selector, beforeNum, waitMs){ return new Promise(function(resolve){ (async function(){ var btn = null; for(var w = 0; w < 24; w++){ btn = document.querySelector(selector); if(btn && !btn.disabled) break; await WAIT(125); } if(!btn || btn.disabled){ return resolve(null); } btn.click(); await WAIT(waitMs); for(var t = 0; t < 12; t++){ var cur = getCurrentNum(); if(cur !== null && cur !== beforeNum){ return resolve(cur); } await WAIT(250); } resolve(null); })(); }); }; var navigateTo = function(num, waitMs){ return new Promise(function(resolve){ (async function(){ var cur = getCurrentNum(); if(cur === null){ return resolve({ status: 'error' }); } if(cur === num){ return resolve({ status: 'ok' }); } for(var step = 0; step < 40; step++){ cur = getCurrentNum(); if(cur === null){ return resolve({ status: 'error' }); } if(cur === num){ return resolve({ status: 'ok' }); } var btnSel = num > cur ? 'button.btn-next-image' : 'button.btn-previous-image'; var newNum = await pressBtn(btnSel, cur, waitMs); if(newNum === null){ return resolve({ status: 'error' }); } if(newNum === num){ return resolve({ status: 'ok' }); } if(num > cur && newNum > num){ return resolve({ status: 'skip', current: newNum }); } if(num < cur && newNum < num){ return resolve({ status: 'skip', current: newNum }); } } resolve({ status: 'error' }); })(); }); }; var waitForCheck = async function(cbk, expectedState){ for(var c = 0; c < 60; c++){ if(cbk.checked === expectedState) break; await WAIT(50); } }; var applyTagsReplace = function(tags, waitMs){ return new Promise(function(resolve){ (async function(){ var allLabels = document.querySelectorAll('label.cm-product-image-tag'); if(allLabels.length === 0) return resolve({ ok: false, msg: 'タグエリアが見つかりません' }); for(var i = 0; i < allLabels.length; i++){ var cb = allLabels[i].querySelector('input[type="checkbox"]'); if(cb && cb.checked){ allLabels[i].click(); await waitForCheck(cb, false); } } await WAIT(200); var spans = document.querySelectorAll('span.cm-product-image-tag-name-text[data-test="tag-name"]'); var available = {}; for(var j = 0; j < spans.length; j++) available[spans[j].textContent.trim()] = true; var valid = tags.filter(function(t){ return available[t]; }).slice(0, 9); var invalid = tags.filter(function(t){ return !available[t]; }); var count = 0; for(var k = 0; k < valid.length; k++){ for(var m = 0; m < spans.length; m++){ if(spans[m].textContent.trim() === valid[k]){ var lbl = spans[m].closest('label.cm-product-image-tag'); if(lbl){ var cbk = lbl.querySelector('input[type="checkbox"]'); if(cbk && !cbk.checked){ lbl.click(); await waitForCheck(cbk, true); count++; break; } } } } } var actualChecked = document.querySelectorAll('label.cm-product-image-tag input[type="checkbox"]:checked').length; var saveSels = ['button[data-test="save-btn"]', 'button[data-test="apply-btn"]', '.cm-save-btn button']; for(var s = 0; s < saveSels.length; s++){ var btn = document.querySelector(saveSels[s]); if(btn && !btn.disabled){ btn.click(); await WAIT(600); break; } } await WAIT(waitMs); resolve({ ok: true, count: count, actual: actualChecked, invalid: invalid }); })(); }); }; var applyTagsAppend = function(tags, waitMs){ return new Promise(function(resolve){ (async function(){ var allLabels = document.querySelectorAll('label.cm-product-image-tag'); if(allLabels.length === 0) return resolve({ ok: false, msg: 'タグエリアが見つかりません' }); var alreadyChecked = {}; for(var i = 0; i < allLabels.length; i++){ var cb = allLabels[i].querySelector('input[type="checkbox"]'); if(cb && cb.checked){ var nameSpan = allLabels[i].querySelector('span.cm-product-image-tag-name-text[data-test="tag-name"]'); if(nameSpan) alreadyChecked[nameSpan.textContent.trim()] = true; } } var checkedCount = Object.keys(alreadyChecked).length; var slots = 9 - checkedCount; if(slots <= 0){ return resolve({ ok: true, count: 0, slots: 0, invalid: [], already: [] }); } var spans = document.querySelectorAll('span.cm-product-image-tag-name-text[data-test="tag-name"]'); var available = {}; for(var j = 0; j < spans.length; j++) available[spans[j].textContent.trim()] = true; var toAdd = tags.filter(function(t){ return available[t] && !alreadyChecked[t]; }).slice(0, slots); var invalid = tags.filter(function(t){ return !available[t]; }); var already = tags.filter(function(t){ return alreadyChecked[t]; }); var count = 0; for(var k = 0; k < toAdd.length; k++){ for(var m = 0; m < spans.length; m++){ if(spans[m].textContent.trim() === toAdd[k]){ var lbl = spans[m].closest('label.cm-product-image-tag'); if(lbl){ var cbk = lbl.querySelector('input[type="checkbox"]'); if(cbk && !cbk.checked){ lbl.click(); await waitForCheck(cbk, true); count++; break; } } } } } var actualChecked = document.querySelectorAll('label.cm-product-image-tag input[type="checkbox"]:checked').length; var saveSels = ['button[data-test="save-btn"]', 'button[data-test="apply-btn"]', '.cm-save-btn button']; for(var s = 0; s < saveSels.length; s++){ var btn = document.querySelector(saveSels[s]); if(btn && !btn.disabled){ btn.click(); await WAIT(600); break; } } await WAIT(waitMs); resolve({ ok: true, count: count, actual: actualChecked, slots: slots, existingCount: checkedCount, invalid: invalid, already: already }); })(); }); }; $('bk-start').onclick = function(){ (async function(){ var raw = $('bk-input').value.trim(); if(!raw){ alert('データを入力してください'); return; } var waitMs = Math.round((parseFloat($('bk-wait').value) || 0.5) * 1000); var mode = currentMode; var data = []; var lines = raw.split('\n'); for(var i = 0; i < lines.length; i++){ var parts = lines[i].trim().split(',').map(function(p){ return p.trim(); }).filter(function(p){ return p; }); if(parts.length < 2) continue; var num = parseInt(parts[0], 10); if(isNaN(num) || num < 1 || num > 40) continue; var rawTags = parts.slice(1).slice(0, 14); var seenTags = {}; var uniqueTags = rawTags.filter(function(t){ return seenTags[t] ? false : (seenTags[t] = true); }); data.push({ num: num, tags: uniqueTags }); } if(data.length === 0){ alert('有効な行がありません。\n形式: 01,タグ1,タグ2,...'); return; } data.sort(function(a, b){ return a.num - b.num; }); if(getCurrentNum() === null){ alert('ページ番号が取得できません。\nスタンプタグ編集ページで実行してください。'); return; } $('bk-start').disabled = true; $('bk-close').disabled = true; $('bk-input').disabled = true; $('bk-wait').disabled = true; $('bk-mode-replace').disabled = true; $('bk-mode-append').disabled = true; collapseToBar(); var errors = [], summary = []; for(var i = 0; i < data.length; i++){ var num = data[i].num; var tags = data[i].tags; var numStr = String(num).padStart(2, '0'); if(cancelRequested){ summary.push(numStr + '番以降: 処理中断(ユーザー操作)'); break; } setStatus('処理中 ' + (i + 1) + ' / ' + data.length + ':' + numStr + '番へ移動中... (現在: ' + getCurrentNum() + '番)'); setBar(i, data.length); var nav = await navigateTo(num, waitMs); if(nav.status === 'error'){ errors.push(numStr + '番: ページ遷移に失敗しました (現在: ' + getCurrentNum() + '番)'); continue; } if(nav.status === 'skip'){ summary.push(numStr + '番: スキップ(ページが存在しません)'); continue; } var ready = false; for(var t = 0; t < 12; t++){ if(document.querySelectorAll('label.cm-product-image-tag').length > 0){ ready = true; break; } await WAIT(250); } if(!ready){ errors.push(numStr + '番: タグエリアが表示されませんでした(待ち時間を増やしてください)'); continue; } setStatus('処理中 ' + (i + 1) + ' / ' + data.length + ':' + numStr + '番にタグ登録中...'); var result; if(mode === 'replace'){ result = await applyTagsReplace(tags, waitMs); } else { result = await applyTagsAppend(tags, waitMs); } if(!result.ok){ errors.push(numStr + '番: ' + result.msg); continue; } var note = ''; if(mode === 'replace'){ if(result.invalid.length > 0) note = ' / 未対応タグ: ' + result.invalid.join(', '); if(result.actual !== undefined && result.actual !== result.count) note += ' ⚠未反映あり(実チェック数:' + result.actual + ')'; summary.push(numStr + '番: ' + result.count + '個登録' + note); var stColor = (result.actual !== undefined && result.actual !== result.count) ? '#e65100' : '#388e3c'; setStatus('✓ ' + numStr + '番: ' + result.count + '個登録' + note, stColor); } else { if(result.slots <= 0) note = '(既に9個登録済みのため追加なし)'; else { if(result.invalid.length > 0) note += ' / 未対応タグ: ' + result.invalid.join(', '); if(result.already.length > 0) note += ' / 登録済みのため除外: ' + result.already.join(', '); note = '(既存' + result.existingCount + '個+追加' + result.count + '個)' + note; } summary.push(numStr + '番: ' + result.count + '個追加 ' + note); setStatus('✓ ' + numStr + '番: ' + result.count + '個追加 ' + note, '#388e3c'); } await WAIT(300); } expandToFull(); if(cancelRequested){ setStatus('⏹ 中断しました。(' + summary.filter(function(s){ return s.indexOf('登録') !== -1 || s.indexOf('追加') !== -1; }).length + '件処理済み)', '#c62828'); } else { setBar(data.length, data.length); setStatus('✅ 完了! ' + data.length + '件処理しました。', '#1b5e20'); } $('bk-start').disabled = false; $('bk-close').disabled = false; $('bk-close').textContent = '閉じる'; $('bk-input').disabled = false; $('bk-wait').disabled = false; $('bk-mode-replace').disabled = false; $('bk-mode-append').disabled = false; if(errors.length > 0){ var errDiv = $('bk-errors'); errDiv.style.display = 'block'; errDiv.innerHTML = '<strong>⚠️ エラー一覧</strong><br>' + errors.join('<br>'); } console.log('[一括タグ登録] 結果:\n' + summary.join('\n')); })(); }; })();免責事項
本ツールはLINEクリエイターズマーケットの非公式ツールです。
LINEのページ構造が変更された場合、動作しなくなる可能性があります。
使用は自己責任でお願いします。
なお、連続実行によるサーバー負荷を考慮し、確認待ち時間を適切に設定してご利用ください。
おわりに
今回の改訂は、コメントでご報告・ご提案いただいた皆さんのフィードバックが大きな力になりました。特に、Chameさんからの助言が、大変参考になりました。ありがとうございます。
使用した際には、フィードバックをいただけると助かります。問題や改善案があればコメントでお知らせください。
以上!
✨ タグ作業をゼロにする
もしよろしければ、合わせて読んでみてください。
このブックマークレットと組み合わせると、 タグ編集の作業がほぼなくなります。
気になる方はこちらもどうぞ。
いいなと思ったら応援しよう!
記事なり、プログラムなりが役に立ったと思った方は、
チップの応援よろしくお願いいたします。
次の開発もがんばろうかな、という気になりますのだ (笑)