usamimi2323

JDownloader2 EventScript - リネーム系アクション集 Ver 0.3

Jan 26th, 2025 (edited)
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 83.86 KB | Source Code | 0 0
  1. /**
  2.  * JDownloader2 EventScript - リネーム系アクション集
  3.  *
  4.  * 『コンテキストメニューから実行』できる
  5.  * リネーム/検索/移動/並べ替えなどの雑多なアクションを集めたモノ
  6.  *
  7.  *
  8.  * 実装済み:
  9.  * ・リネーム各種
  10.  * ・ローカルファイル検索(EveryThing)
  11.  * ・WEB検索(ブラウザ)
  12.  * ・特定ホストだけを有効/無効
  13.  * ・パッケージ並べ替え
  14.  * ・リンクグラバーに再登録
  15.  * ・名前付き新しいパッケージに移動
  16.  *  (既存の標準メニューにも「新しいパッケージに移動」は存在するが、
  17.  *  ダイアログが開くのでその無駄を省く)
  18.  *
  19.  * なお、日本語ファイル名でのJD2への登録とファイル保存運用が基本
  20.  * 作者名やタイトルに関しては、日本語ファイル名の命名規則に
  21.  * 則っている必要がある
  22.  *
  23.  * 命名規則例: (カテゴリ) [作者名A×作者名B] タイトル 第01-25巻
  24.  *
  25.  *
  26.  * JDについては、Windows環境しかないので他環境は関知しません
  27.  *
  28.  *
  29.  * 当該コードを利用することで生じるいかなる損失、被害、影響もコード製作者は責任を負いません
  30.  * 改変転載自由、全て自己責任で
  31.  *
  32.  *
  33.  * 【使い方】
  34.  *
  35.  *
  36.  * ■[トリガー]
  37.  *    『ダウンロードリストコンテキストメニュー選択時』
  38.  *    『リンクグラバーリストコンテキストメニュー選択時』
  39.  *
  40.  *    同一スクリプトで両方に対応可能
  41.  *
  42.  * ~~ スクリプト内の【設定ここから】部分を、環境に合わせて書き換える ~~
  43.  *
  44.  *
  45.  * ■[WEB検索]
  46.  *
  47.  *  Config.web_search_table内の設定を自分用に書き換える
  48.  *  自動ならリネーム系アクション集用メニューインポーターを書き換える
  49.  *  手動ならnameをメニュー名に設定する
  50.  *
  51.  *
  52.  *
  53.  * ■[コンテキストメニューへの登録]
  54.  *
  55.  *  □自動設定
  56.  *   別途、自動登録用スクリプトを使用することで自動で登録可能
  57.  *
  58.  * @see https://pastebin.com/NR7J5g58
  59.  *      "JDownloader2 EventScript - リネーム系アクション集用コンテキストメニュー自動設定"
  60.  *
  61.  *
  62.  *  □手動設定
  63.  *   ダウンロードリストコンテキストメニュー、リンクグラバーコンテキストメニュー
  64.  *   メニュー管理画面→「アクション追加」or「+」ボタン→「EventScripter Trigger」
  65.  *   →プロパティの「名前」を下記「メニュー名」に設定
  66.  *
  67.  *
  68.  * -----------------------------------------------------------------------------------------------------------
  69.  * メニュー名                                 説明
  70.  * -----------------------------------------------------------------------------------------------------------
  71.  * EveryThingで検索(タイトル)                 EveryThingを検索する((△△) [○○] □□ の□□を検索ワードに)
  72.  * EveryThingで検索(作者名)                   EveryThingで検索する((△△) [○○] □□ の○○を検索ワードに)
  73.  * ブラウザで開く                             設定したブラウザで開く
  74.  *
  75.  * JDバックアップフォルダを開く
  76.  * 登録元ページを開く                         登録元(リンクにsourceUrlがあれば)を開く
  77.  *
  78.  * パッケージ名で名前を揃える                 パッケージ名でパッケージ内のリンクの名前を揃える
  79.  *
  80.  * このリンクの名前でパッケージ内を全て揃える 選択しているリンクの名前で、パッケージとパッケージ内のリンクの名前を揃える
  81.  *                                            (各パッケージ内でリンクを一つだけ選択)
  82.  *
  83.  * 名前を揃える                               右クリックされたリンク(もしくは選択された一番上のリンクの名前)で
  84.  *                                            選択されているリンクの名前を揃える
  85.  *                                            ※1
  86.  *
  87.  * 名前を揃える(作者名)                       選択などは※1と同様 (○○) [△△] □□ の[△△]を揃える
  88.  * 名前を揃える(タイトル)                     選択などは※1と同様 (○○) [△△] □□ の□□を揃える
  89.  * 名前を揃える(カテゴリ+作者名)              選択などは※1と同様 (○○) [△△] □□ の(○○) [△△]を揃える
  90.  * 名前を揃える(カテゴリ+作者名+タイトル)     選択などは※1と同様 (○○) [△△] □□ 第01-04巻の(○○) [△△] □□を揃える
  91.  *
  92.  * 第○○巻化                                 v01を第01巻にリネーム
  93.  *
  94.  * []内のスワップ                             [○○×△△]→[△△×○○]
  95.  * []内の末尾切り捨て                         [○○×△△×□□]→[○○×△△]
  96.  * []内のxを×に変換                          [○○x△△]→[○○×△△]
  97.  *
  98.  * 名前の数字部分の桁を揃える                 名前の数字の桁を揃える(0埋め、最小桁数=2)
  99.  *
  100.  *
  101.  * ○○ >>                                    「【追加したい文字】   >>」
  102.  *                                            リンクまたはパッケージの名前の先頭に○○を追加する
  103.  *                                            (○○) でカテゴリ名の場合は、追加ではなく置換する
  104.  *
  105.  * << ××                                    「<<   【追加したい文字】」
  106.  *                                            リンクまたはパッケージの名前の終端に××を追加する
  107.  *
  108.  *
  109.  * この名前を使用して新しいパッケージに移動   右クリックされたリンクまたはパッケージの名前を使用し
  110.  *                                            選択されたリンクまたはパッケージを
  111.  *                                            新しいパッケージに移動し、まとめる
  112.  *                                            
  113.  * 同じ名前のパッケージをまとめる             同じ名前のパッケージをまとめる
  114.  *
  115.  * リンクグラバーへ再登録                     選択されたリンクまたはパッケージをリンクグラバーに再登録する
  116.  *
  117.  *
  118.  * タイトルで並べ替え(昇順)                   (△△) [○○] □□……選択されたパッケージを□□で昇順並べ替え
  119.  * タイトルで並べ替え(降順)                   (△△) [○○] □□……選択されたパッケージを□□で降順並べ替え
  120.  * 作者名で並べ替え(昇順)                     (△△) [○○] □□……選択されたパッケージを[○○]で昇順並べ替え
  121.  * 作者名で並べ替え(降順)                     (△△) [○○] □□……選択されたパッケージを[○○]で降順並べ替え
  122.  * 追加日時で並べ替え(昇順)                   選択されたパッケージを追加日時で昇順並べ替え
  123.  * 追加日時で並べ替え(降順)                   選択されたパッケージを追加日時で降順並べ替え
  124.  *
  125.  * 優先ホスト(単一)以外を無効                 選択しているパッケージまたはリンクのURLを
  126.  *                                            優先順のマッチングリストの先頭から順にマッチングしていき、
  127.  *                                            該当したら、それ以外のパッケージ内のリンクを無効にする
  128.  *                                            (UserConfig.disableLinksMyRule で設定可能)
  129.  * 優先ホスト(複数)以外を無効                 選択しているパッケージまたはリンクのURLが
  130.  *                                            マッチングリストにマッチするか否かで、パッケージ内のリンクを有効/無効にする
  131.  *                                            (UserConfig.disableLinksMyRule で設定可能)
  132.  *
  133.  * hogehoge.com以外を無効                     選択されたリンクからホスト名hogehoge.comを含まないリンクを無効にする
  134.  * hogehoge.comを無効                         選択されたリンクからホスト名hogehoge.comを含んだリンクを無効にする
  135.  *                                            ※hogehoge.comには任意のホスト名を指定可能
  136.  *
  137.  * ブラウザで開く - hogehoge.comのみ          選択されたリンクからホスト名hogehoge.comを含んだリンクのURLをブラウザで開く
  138.  *                                            ※hogehoge.comには任意のホスト名を指定可能
  139.  * -----------------------------------------------------------------------------------------------------------
  140.  *
  141.  *
  142.  * JD2のEventScriptでは、メインメニュー、ツールバー、トレイアイコンメニューの
  143.  * イベントトリガーが機能しない……
  144.  *
  145.  *
  146.  **/
  147.  
  148. disablePermissionChecks();
  149.  
  150. /** @enum {number} */
  151. const gkPart={
  152.     author: 1,
  153.     author_1st: 2,
  154.     title: 3
  155. };
  156.  
  157. const UserConfig =
  158. {
  159. // 【設定ここから】
  160.  
  161.     path:
  162.     {
  163.         everything : 'C:\\Program Files\\Everything\\Everything.exe',
  164.         browser    : 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
  165.         filer      : 'C:\\windows\\explorer.exe',
  166. //      filer      : getEnv('WINDIR')+'\\explorer.exe',
  167.     },
  168.  
  169.     web_search_table: {
  170.     //
  171.     //
  172.     // 書式 'name': [part,'URL'],
  173.     //
  174.     // name  メニュー名
  175.     //
  176.     // part  検索ワードに使うファイル名の部分
  177.     //       (gkPart.title|gkPart.author|gkPart.author_1st)
  178.     //
  179.     //
  180.     // 【例】(一般小説) [ワシ×オマエ] ドM哲学 ~深淵編~ 第01巻.zip
  181.     //
  182.     //   gkPart.title       タイトル部分         【例】 検索ワード'ドM哲学'
  183.     //   gkPart.author      []内の作者名部分全て 【例】 検索ワード'ワシ OR オマエ'
  184.     //   gkPart.author_1st  []内の作者名部分先頭 【例】 検索ワード'ワシ'
  185.     //
  186.     // URL   %REP%が検索ワードに置換されたURLがブラウザに渡される
  187.     //       (デフォルト値) null => "https://name/?s=%REP%"
  188.     //
  189.     //------------------------------------------------------------------------------------------------------------
  190.    
  191.         //*****[ ↓自分用に書き換える ]*****/
  192.         'x3**.net'              : [gkPart.author_1st,   null],
  193.         'bs***.com'             : [gkPart.author_1st,   null],
  194.         '*****-zip.info'        : [gkPart.title,        'https://*****-zip.info/search/%REP%/'],
  195.         '888**.**'              : [gkPart.author_1st,   'https://888**.**/search.php?q=%REP%'],
  196.         '******nexus.com'       : [gkPart.author_1st,   'https://******nexus.com/public/search.php?x=12&y=17&q=%REP%'],
  197.         '678**.***'             : [gkPart.author_1st,   null],
  198.         '*****77.***'           : [gkPart.author_1st,   null],
  199.         'dl-***.***'            : [gkPart.author_1st,   null],
  200.         'raw*****.cc'           : [gkPart.author_1st,   null],
  201.         '*****-zone.***'        : [gkPart.author_1st,   'http://www.*****-zone.***/?submit=Search&s=%REP%'],
  202.         '******core.***'        : [gkPart.author_1st,   null],
  203.         '******omg.***'         : [gkPart.author_1st,   null],
  204.         'nhentai.net'           : [gkPart.author_1st,   'https://nhentai.net/search/?q=%REP%'],
  205.  
  206.         'google.com (作者)'       : [gkPart.author,   'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  207.         'google.com (タイトル)' : [gkPart.title,    'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  208.        
  209.         'google.com (Author)'   : [gkPart.author,   'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  210.         'google.com (Title)'    : [gkPart.title,    'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  211.         //*****[ ↑自分用に書き換える ]*****/
  212.     },
  213.  
  214.     // 「優先ホスト(○○)以外を無効」で使用するルール
  215.     // (正規表現オブジェクトの配列、(単一)なら先頭からマッチングしていき、
  216.     // マッチングすればそれ一つ以外のパッケージ内のリンクを無効化
  217.     // (複数)ならマッチングしたもの以外を無効化)
  218.     disableLinksMyRule:     [
  219.                                 /frdl\.to/,             // 時折、不安定。reCaptchaが必要になったり、エラー出たり
  220.                                 /hexload\.com/,         // hCaptcha
  221.                                 /dailyuploads\.net/     // reCaptchaが必須になった、クールタイムが極小、制限少ない
  222.                             ],
  223.  
  224.  
  225.     open_browser_interval: 2000,        // 複数のURLをブラウザで開く時の間隔(ミリ秒)
  226.     open_browser_max:10,                // 複数のURLをブラウザで開く時の上限数
  227.    
  228.     waittime_for_expand_package:100,    // パッケージ/リンク移動後にツリー展開するまでの待機時間(ミリ秒)
  229.     expand_after_newpackage: false,     // パッケージに移動後にツリー展開したくない場合はfalseにする
  230.  
  231. // 【設定ここまで】
  232. };
  233.  
  234. UserConfig.initConfig = function()
  235. {
  236.     keys(this.path).forEach(function(k)
  237.     {
  238.         if (!this.path[k] || !getPath(this.path[k]).isFile())
  239.             throw("ERROR: Config.path."+k+" is not file path.");
  240.     },this);
  241.     (this.disableLinksMyRule).forEach(function(k){
  242.         if (! isRegExp(k))
  243.             throw("ERROR: Config.disableLinksMyRule, Not RegExp : "+k.toString());
  244.     },this);
  245.     keys(this.web_search_table).forEach(function(k){
  246.         const e = this.web_search_table[k];
  247.         if (!keys(gkPart).some(function(kk){return gkPart[kk] === e[0]},this))
  248.             throw("ERROR: Config.web_search_table, Not gkPart : "+v);
  249.         if (e[1] != null && !/^https?:\/\/.+/.test(e[1]))
  250.             throw("ERROR: Config.web_search_table, Not URL or null: "+e[1]);
  251.     },this);
  252. };
  253.  
  254. const LANGUAGE_JA      = 'ja';
  255. const LANGUAGE_EN      = 'en';
  256. const LANGUAGE_DEFAULT = LANGUAGE_EN;
  257.  
  258. const APIMethod={
  259.     queryLinks:'queryLinks',
  260.     addLinks:'addLinks',
  261.     moveLinks:'moveLinks',
  262.     movePackages:'movePackages',
  263.     movetoNewPackage:'movetoNewPackage',
  264. };
  265.  
  266. /** @enum {number} buttonEvents*/
  267. const buttonEvents =
  268. {
  269.     NONE:                                  0,
  270.     DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON:    1<<0,
  271.     LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON: 1<<1,
  272.     DOWNLOAD_TABLE_BOTTOM_BAR_BUTTON:      1<<2,
  273.     LINKGRABBER_BOTTOM_BAR_BUTTON:         1<<3,
  274. };
  275. buttonEvents.EVENT_DLC          = buttonEvents.DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON | buttonEvents.DOWNLOAD_TABLE_BOTTOM_BAR_BUTTON;
  276. buttonEvents.EVENT_LGC          = buttonEvents.LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON | buttonEvents.LINKGRABBER_BOTTOM_BAR_BUTTON;
  277. buttonEvents.EVENT_CONTEXT_MENU = buttonEvents.DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON | buttonEvents.LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON;
  278. buttonEvents.EVENT_DLC_AND_LGC  = buttonEvents.EVENT_DLC | buttonEvents.EVENT_LGC;
  279. buttonEvents.isDLC  = function(b){return 0!=((this[b]||0)&buttonEvents.EVENT_DLC)};
  280. buttonEvents.isLGC  = function(b){return 0!=((this[b]||0)&buttonEvents.EVENT_LGC)};
  281.  
  282. function zeroPadding(s,l){return((s.length<l?'0'.repeat(l-s.length):'')+s)}
  283. function DQ(s){ return '"'+ s.trim() +'"'}
  284. function unDQ(s){return s.replace(/^[\s\xA0]*"?|"?[\s\xA0]*$/g, '')}
  285. function keys(k){return Object.keys(k)}
  286. function isRegExp(x){return Object.prototype.toString.call(x) === '[object RegExp]'}
  287. function isFunction(x){return typeof x === 'function'}
  288. function isString(x){return typeof x === 'string'}
  289. function T(s){return (s===undefined||s===null)?'':s}
  290. function getFileParts(f)
  291. {
  292.     const r = T(f).match(/^(.+?)(\.part\d+|\.mp3|\.zip|\.rar|\.tar|\.epub)?(\.[\da-z]{2,6})$/i);
  293.     return (r && !/^\d+$/.test(r[3]))?[T(r[1]),T(r[2]),T(r[3])]:[T(f),'',''];
  294. }
  295. function getFileSpec(f){return (getFileParts(f))[0]}
  296. function getFileSpecFull(f){const x=getFileParts(f);return x[0]+x[1];}
  297. function getExt(f){return (getFileParts(f))[2]}
  298. function getExtFull(f){const x=getFileParts(f);return x[1]+x[2];}
  299.  
  300. /**
  301.  * 文字列の正規化
  302.  *
  303.  * @param   {string} s 変換前の文字列
  304.  * @returns {string} 変換後の文字列
  305.  *
  306.  * ES5環境にはString.prototype.normalize()が無い
  307.  */
  308. function normalizeTitle(s)
  309. {
  310.     if (s==null||s==="") return s;
  311.    
  312.     /** @type {object.<string:string>} 半角記号の変換テーブル */
  313.     const NT1 = {
  314. "\!":"!","\"":"”","\$":"$","\%":"%","\&":"&","'":"’","~":" ̄","|":"|",
  315. "*":"*",":":":","<":"<",">":">","?":"?","/":"/","\\":"¥","`":"‘",",":","
  316. //特殊濁点半濁点→日本語濁点半濁点(分離)
  317. //"\u3099":"\u309B","\u309A":"\u309C"
  318.     };
  319.     /** @type {object.<string:string>}} 文字列の変換テーブル */
  320.     const NT2 = {
  321. //記号
  322. " ":" ","〜":"~","&times;":"+","&amp;":"&","♡":"","♥":"","―":"-","─":"-","━":"-",
  323.  
  324. //半角カナ→全角カナ
  325. 'ガ': 'ガ', 'ギ': 'ギ', 'グ': 'グ', 'ゲ': 'ゲ', 'ゴ': 'ゴ',
  326. 'ザ': 'ザ', 'ジ': 'ジ', 'ズ': 'ズ', 'ゼ': 'ゼ', 'ゾ': 'ゾ',
  327. 'ダ': 'ダ', 'ヂ': 'ヂ', 'ヅ': 'ヅ', 'デ': 'デ', 'ド': 'ド',
  328. 'バ': 'バ', 'ビ': 'ビ', 'ブ': 'ブ', 'ベ': 'ベ', 'ボ': 'ボ',
  329. 'パ': 'パ', 'ピ': 'ピ', 'プ': 'プ', 'ペ': 'ペ', 'ポ': 'ポ',
  330. 'ヴ': 'ヴ', 'ヷ': 'ヷ', 'ヺ': 'ヺ',
  331. 'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
  332. 'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
  333. 'サ': 'サ', 'シ': 'シ', 'ス': 'ス', 'セ': 'セ', 'ソ': 'ソ',
  334. 'タ': 'タ', 'チ': 'チ', 'ツ': 'ツ', 'テ': 'テ', 'ト': 'ト',
  335. 'ナ': 'ナ', 'ニ': 'ニ', 'ヌ': 'ヌ', 'ネ': 'ネ', 'ノ': 'ノ',
  336. 'ハ': 'ハ', 'ヒ': 'ヒ', 'フ': 'フ', 'ヘ': 'ヘ', 'ホ': 'ホ',
  337. 'マ': 'マ', 'ミ': 'ミ', 'ム': 'ム', 'メ': 'メ', 'モ': 'モ',
  338. 'ヤ': 'ヤ', 'ユ': 'ユ', 'ヨ': 'ヨ',
  339. 'ラ': 'ラ', 'リ': 'リ', 'ル': 'ル', 'レ': 'レ', 'ロ': 'ロ',
  340. 'ワ': 'ワ', 'ヲ': 'ヲ', 'ン': 'ン',
  341. 'ァ': 'ァ', 'ィ': 'ィ', 'ゥ': 'ゥ', 'ェ': 'ェ', 'ォ': 'ォ',
  342. 'ッ': 'ッ', 'ャ': 'ャ', 'ュ': 'ュ', 'ョ': 'ョ',
  343. '。': '。', '、': '、', 'ー': 'ー', '「': '「', '」': '」', '・': '・',
  344. //よくある簡体字、繁体字→日本語常用漢字
  345. '卷':'巻','學':'学','黑':'黒','關':'関','繪':'絵','會':'会','亞':'亜','髙':'高','說':'説','户':'戸',
  346. '說':'説','说':'説'     //繁体字、簡体字の"説"
  347.     };
  348.    
  349.     /** @type {object.<string:string>} 濁点半濁点結合 */
  350.     const NT3={
  351. 'ウ゛':'ヴ','ワ゛':'ヷ','ヲ゛':'ヺ',
  352. 'カ゛':'ガ','キ゛':'ギ','ク゛':'グ','ケ゛':'ゲ','コ゛':'ゴ',
  353. 'サ゛':'ザ','シ゛':'ジ','ス゛':'ズ','セ゛':'ゼ','ソ゛':'ゾ',
  354. 'タ゛':'ダ','チ゛':'ヂ','ツ゛':'ヅ','テ゛':'デ','ト゛':'ド',
  355. 'ハ゛':'バ','ヒ゛':'ビ','フ゛':'ブ','ヘ゛':'ベ','ホ゛':'ボ',
  356. 'ハ゜':'パ','ヒ゜':'ピ','フ゜':'プ','ヘ゜':'ペ','ホ゜':'ポ',
  357. 'か゛':'が','き゛':'ぎ','く゛':'ぐ','け゛':'げ','こ゛':'ご',
  358. 'さ゛':'ざ','し゛':'じ','す゛':'ず','せ゛':'ぜ','そ゛':'ぞ',
  359. 'た゛':'だ','ち゛':'ぢ','つ゛':'づ','て゛':'で','と゛':'ど',
  360. 'は゛':'ば','ひ゛':'び','ふ゛':'ぶ','へ゛':'べ','ほ゛':'ぼ',
  361. 'は゜':'ぱ','ひ゜':'ぴ','ふ゜':'ぷ','へ゜':'ぺ','ほ゜':'ぽ',
  362.     };
  363. //  var pat1=new RegExp('(?:['+Object.keys(NT1).map(c=>'\\'+c).join('')+']', 'g');
  364. //  alert('(?:['+Object.keys(NT1).join('')+']');
  365.  
  366.     const pat1=new RegExp('['+keys(NT1).join('')+']', 'g');
  367.     const pat2=new RegExp('(?:' + keys(NT2).join('|') + ')', 'g');
  368. //  const pat3=new RegExp('(['+Object.keys(NT3).map(function(s){return s[0]}).join('')+'])([\u3099\u309B\u309A\u309C])', 'g');
  369.     const pat3=/([ウワヲカキクケコサシスセソタチツテトハヒフヘホうかきくけこさしすせそたちつてとはひふへほ])([\u3099\u309B\u309A\u309C])/g;
  370.     return( s
  371.             .replace(/[A-Za-z0-9]/g, function(s){return String.fromCharCode(s.charCodeAt(0)-0xFEE0)})
  372.             .replace(pat1,function(m){return(m in NT1?NT1[m]:m)})
  373.             .replace(pat2,function(m){return(m in NT2?NT2[m]:m)})
  374.             .replace(pat3,function(m,s1,s2){var x=s1+{'\u3099':'\u309B','\u309B':'\u309B','\u309A':'\u309C','\u309C':'\u309C'}[s2];return(x in NT3?NT3[x]:x)})
  375.             .replace(/([\]\)])([^\s\.])/g, '$1 $2')
  376.             .replace(/([^ ])([\[\(])/g, '$1 $2')
  377.             .replace(/  +/g," ")
  378.         );
  379. }
  380.  
  381. function getLinkURL(l){return l.contentURL||l.pluginURL||''}
  382.  
  383. function jdPath(path)
  384. {
  385.     const sep = getPathSeparator();
  386.     if (sep == '\\')
  387.         path = path.replace('/', '\\');
  388.     else if (sep == '/')
  389.         path = path.replace('\\', '/');
  390.     return JD_HOME+sep+path;
  391. }
  392.  
  393. function jdFormatString(str, replacers)
  394. {
  395.     return str.replace(/%s(\d)/g,function(m,n){return --n<replacers.length?replacers[n]:m})
  396.         .replace(/\\([\\"nrt])/g, function(m,m1){return {'\\':'\\','"':'"','n':'\n','r':'\r','t':'\t'}[m1]});   //"
  397. }
  398.  
  399. function getDefaultDownloadFolder()
  400. {
  401.     return callAPI("config", "get", "org.jdownloader.settings.GeneralSettings", null, "DefaultDownloadFolder");
  402. }
  403.  
  404. /**
  405.  * パッケージ上で右クリックしたのなら選択中のパッケージリストを
  406.  *     リンク上で右クリックしたのなら選択中のリンクリストを返す
  407.  *
  408.  * @function
  409.  * @param   {object} sel lgSelection or dlSelection
  410.  * @returns {object[]}   packages or links or []
  411.  */
  412. function getItemsByContextType(sel)
  413. {
  414.     return (sel.isPackageContext() ? sel.packages : (sel.isLinkContext() ? sel.links : []));
  415. }
  416.  
  417. /**
  418.  * 右クリックされたパッケージかリンクを返す
  419.  *
  420.  * @param   {object} sel  lgSelection or dlSelection
  421.  * @returns {object|null} package or link or null
  422.  */
  423. function getContextItemByContextType(sel)
  424. {
  425.     return (sel.isPackageContext() ? sel.contextPackage : (sel.isLinkContext() ? sel.contextLink : null));
  426. }
  427.  
  428.  
  429.  
  430. // "Script" が使えない(´・ω・`)
  431. const App = {
  432.     config:null,
  433.     translation:null,
  434.     resource:null,
  435.     dispatcher:null,
  436. };
  437.  
  438. App.config      = UserConfig;
  439.  
  440. /**
  441.  * JDの言語ファイル関連
  442.  *
  443.  *   JD_UISetting_Language = translation.language;
  444.  *   translation.isAutoCreatedPackageName(package_name)
  445.  *   translation.isDefaultPackageName(package_name)
  446.  *
  447.  * 今のところ、取り扱いは以下の3ファイル
  448.  *   cfg/language.json
  449.  *   translations/org/jdownloader/translate/JdownloaderTranslation.*.lng
  450.  *   translations/org/jdownloader/gui/translate/GuiTranslation.*.lng
  451.  */
  452. App.translation =
  453. {
  454.     VARIOUS_PACKAGE                 : 'LinkCollector_addCrawledLink_variouspackage',    // 様々なファイル
  455.     DEFAULT_PACKAGE                 : 'controller_packages_defaultname',                // 任意
  456.     OFFLINE_PACKAGE                 : 'LinkCollector_addCrawledLink_offlinepackage',    // オフラインファイル
  457.     PERMANENTLY_OFFLINE_PACKAGE     : 'Permanently_Offline_Package',                    // 永続オフライン
  458.     HEXLOAD_PACKAGE                 : 'Folder',                                     // hexload.com plugin
  459.     PATH_CFG_LANGUAGE_JSON          : 'cfg\\language.json',
  460.     PATH_TRANSLATION_GUITRANSLATE   : 'translations\\org\\jdownloader\\gui\\translate',
  461.     PATH_TRANSLATION_TRANSLATE      : 'translations\\org\\jdownloader\\translate',
  462.     PREFIX_TRANSLATION_GUITRANSLATE : 'GuiTranslation',
  463.     PREFIX_TRANSLATION_TRANSLATE    : 'JdownloaderTranslation',
  464.     EXTENTION_LNG_FILE              : '.lng',
  465.    
  466.     /**
  467.      * JDのlanguageを取得(JD本体側の「UI設定の言語」)
  468.      *
  469.      * @return {string='en'}
  470.      *
  471.      * 返すlanguage文字列は[-a-zA-Z\d_]+の範囲内の文字列("en"|"ja"|etc...)
  472.      * デフォルトは en
  473.      *
  474.      * ファイル'cfg\\language.json'から取得
  475.      * この文字列はJDをリスタートするまで不変
  476.      * 値はキャッシュする
  477.      */
  478.     _language : null,
  479.     get language() {
  480.             if (this._language) return this._language;
  481.             const buf = unDQ(readFile(jdPath(this.PATH_CFG_LANGUAGE_JSON)));
  482.             return this._language = (buf != '' && /^[-a-zA-Z\d_]+$/.test(buf)) ? buf : 'en';
  483.     },
  484.    
  485.     /**
  486.      * JDの言語ファイル(*.lng)を読み込み変換テーブルを取得
  487.      * ファイル毎の最小限のキャッシュを生成(ファイル全体のデータは破棄し、指定したキーのみキャッシュ)
  488.      *
  489.      * @param {string} path_lng 取得する言語ファイルへのパス
  490.      * @param {string[]|string} filter_keys 取得する文字列を指定するキーorキー配列、引数無しで全取得
  491.      * @return {TranslateResourceTable|null} 指定した言語ファイルから取得した文字列変換テーブル
  492.      * -@return {object.<string,string>|null}
  493.      *
  494.      * ※中身が/(key:[^=\s]+)=(value:[^\n]*)\n/のファイルを読む関数
  495.      *
  496.      */
  497.     getTranlation : function(path_lng, filter_keys)
  498.     {
  499.         if (! this.getTranlation.cache) this.getTranlation.cache = {};
  500.        
  501.         const ret = {};
  502.         if (filter_keys)
  503.         {
  504.             if (!Array.isArray(filter_keys))
  505.             {
  506.                 if (typeof filter_keys !== 'string')
  507.                     return null;
  508.                
  509.                 filter_keys = [filter_keys];
  510.             }
  511.            
  512.             if (this.getTranlation.cache[path_lng])
  513.             {
  514.                 filter_keys = filter_keys.filter(function(k)
  515.                 {
  516.                     if (! (this.getTranlation.cache[path_lng][k]) )
  517.                         return true;
  518.                    
  519.                     ret[k] = this.getTranlation.cache[path_lng][k];
  520.                     return false;
  521.                 }, this);
  522.                 if (keys(filter_keys).length == 0)
  523.                     return ret;
  524.             }
  525.         }
  526.         const buf = readFile(path_lng);
  527.         if (! buf) return null;
  528.         if (! this.getTranlation.cache[path_lng]) this.getTranlation.cache[path_lng] = {};
  529.        
  530.         const _cache = this.getTranlation.cache[path_lng];
  531.         if (! filter_keys)
  532.             buf.replace(/([^=\n]+)=([^\n]*)/g,function(line,k,v){_cache[k]=ret[k]=v});
  533.         else if (Array.isArray(filter_keys))
  534.             buf.replace(/([^=\n]+)=([^\n]*)/g,function(line,k,v){if(0<filter_keys.filter(function(a){return a==k}).length)_cache[k]=ret[k]=v});
  535.         else if (typeof filter_keys === 'string')
  536.             buf.replace(/([^=\n]+)=([^\n]*)/g,function(line,k,v){if(filter_keys==k)_cache=ret[k]=v});
  537.         return ret;
  538.     },
  539.    
  540.     _getJdownloaderTranslationXLngPath : function(lng){
  541.         return jdPath(this.PATH_TRANSLATION_TRANSLATE+'\\'+this.PREFIX_TRANSLATION_TRANSLATE+'.'+(lng||this.language)+this.EXTENTION_LNG_FILE)
  542.     },
  543.     _getGuiTranslationXLngPath : function(lng){
  544.         return jdPath(this.PATH_TRANSLATION_GUITRANSLATE+'\\'+this.PREFIX_TRANSLATION_GUITRANSLATE+'.'+(lng||this.language)+this.EXTENTION_LNG_FILE)
  545.     },
  546.    
  547.     /**
  548.      * JDの言語ファイル JdownloaderTranslation.{language}.lngを読み込んで変換テーブルを取得
  549.      * ファイル毎の最小限のキャッシュを生成(ファイル全体のデータは破棄し、指定したキーのみキャッシュ)
  550.      *
  551.      * @param {string[]|string} filter_keys 取得したいキーorキー配列、引数無しで全取得
  552.      * @param {string=this.language} lang 言語の指定(デフォルトは「JDのUI設定の言語」)
  553.      * @return {object.<string,string>|null} 指定した言語ファイルから取得した文字列変換テーブル
  554.      */
  555.     getJdownloaderTranslation : function(filter_keys, lang){
  556.         return this.getTranlation(this._getJdownloaderTranslationXLngPath(lang||this.language), filter_keys)
  557.     },
  558.     /**
  559.      * JDの言語ファイル GuiTranslation.{language}.lngを読み込んで変換テーブルを取得
  560.      * ファイル毎の最小限のキャッシュを生成(ファイル全体のデータは破棄し、指定したキーのみキャッシュ)
  561.      *
  562.      * @param {string[]|string} filter_keys 取得したいキーorキー配列、引数無しで全取得
  563.      * @param {string=this.language} lang 言語の指定(デフォルトは「JDのUI設定の言語」)
  564.      * @return {object.<string,string>|null} 指定した言語ファイルから取得した文字列変換テーブル
  565.      */
  566.     getGuiTranslation : function(filter_keys, lang){
  567.         return this.getTranlation(this._getGuiTranslationXLngPath(lang||this.language), filter_keys)
  568.     },
  569.    
  570.     /**
  571.      * 自動生成されたパッケージ名かどうか判定(英語+現在の言語、2言語分チェック)
  572.      *
  573.      * @param {string} n パッケージ名
  574.      * @return {boolean}
  575.      *
  576.      * JDownloaderの言語パッケージから翻訳データを取得しないと判別できない
  577.      * Translationインターフェースの使い方が分からんから
  578.      * .lngファイルから直読み
  579.      *
  580.      **/
  581.     isAutoCreatedPackageName : function(n)
  582.     {
  583.         const langs = [this.language];
  584.         if (langs[0] != LANGUAGE_DEFAULT) langs.push(LANGUAGE_DEFAULT);
  585.         return langs.some(function(l)
  586.         {
  587.             const tmp1 = this.getJdownloaderTranslation([this.VARIOUS_PACKAGE, this.DEFAULT_PACKAGE, this.OFFLINE_PACKAGE], l);
  588.             const tmp2 = this.getGuiTranslation([this.PERMANENTLY_OFFLINE_PACKAGE], l);
  589.            
  590.             return n===tmp1[this.VARIOUS_PACKAGE]
  591.                 || n===tmp1[this.DEFAULT_PACKAGE]
  592.                 || n===tmp1[this.OFFLINE_PACKAGE]
  593.                 || n===tmp2[this.PERMANENTLY_OFFLINE_PACKAGE];
  594.         },this);
  595.     },
  596.  
  597.     /**
  598.      * デフォルトのパッケージ名かどうか判定(英語+現在の言語、2言語分チェック)
  599.      *
  600.      * @param {string} n パッケージ名
  601.      * @return {boolean}
  602.      **/
  603.     isDefaultPackageName : function(n)
  604.     {
  605.         const langs = [this.language];
  606.         if (langs[0] != LANGUAGE_DEFAULT) langs.push(LANGUAGE_DEFAULT);
  607.         return langs.some(function(l)
  608.         {
  609.             const tmp = this.getJdownloaderTranslation([this.VARIOUS_PACKAGE, this.DEFAULT_PACKAGE], l);
  610.             return n===tmp[this.VARIOUS_PACKAGE]
  611.                 || n===tmp[this.DEFAULT_PACKAGE];
  612.         },this);
  613.     },
  614. };
  615. App.isAutoCreatedPackageName = function(n){return this.translation.isAutoCreatedPackageName(n)};
  616. App.isDefaultPackageName     = function(n){return this.translation.isDefaultPackageName(n)};
  617. App.getResource = function(id, lang){return this.resource.getResource(id, lang||this.script_language)};
  618.  
  619. App.isDLC              = function(){return buttonEvents.isDLC(this.menu)};
  620. App.isLGC              = function(){return buttonEvents.isLGC(this.menu)};
  621. App.isDownloadTable    = function(){return(this.menu == "DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON")};
  622. App.isLinkGrabberTable = function(){return(this.menu == "LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON")};
  623. App.getSelection       = function(){return this.context.selection};
  624. App.checkEventSource   = function(allowedEvents){return buttonEvents[this.menu]&&0!=(buttonEvents[this.menu]&allowedEvents)};
  625. App.initResourceTables = function()
  626. {
  627.     var r = {
  628.         'ja':
  629.         {
  630.             OPEN_EVERYTHING_BY_TITLE                    : 'EveryThingで検索(タイトル)',
  631.             OPEN_EVERYTHING_BY_AUTHOR                   : 'EveryThingで検索(作者名)',
  632.             OPEN_LINK                                   : 'ブラウザで開く',
  633.            
  634.             OPEN_JD_CFG_FOLDER                          : 'JDバックアップフォルダを開く',
  635.             OPEN_SOURCEURL                              : '登録元ページを開く',
  636.            
  637.     // リネーム系
  638.             RENAME_LINKS_BY_PACKAGENAME                 : 'パッケージ名で名前を揃える',
  639.             RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME : 'このリンクの名前でパッケージ内を全て揃える',
  640.  
  641.             RENAME_LINK_BY_LINKNAME                     : '名前を揃える',     // 複数のリンクを選択してから右クリックし、右クリック直下のファイル名 (○○) [△△] □□.zip→(○○) [△△] □□を揃える
  642.             RENAME_LINK_BY_LINK_AUTHOR                  : '名前を揃える(作者名)',  // (○○) [△△] □□.zip→[△△]部を揃える
  643.             RENAME_LINK_BY_LINK_TITLE                   : '名前を揃える(タイトル)',   // (○○) [△△] □□ ~××~第01巻.zip→□□部を揃える
  644.             RENAME_LINK_BY_LINK_CATEGORY_AUTHOR         : '名前を揃える(カテゴリ+作者名)',
  645.             RENAME_LINK_BY_LINK_LONGTITLE               : '名前を揃える(カテゴリ+作者名+タイトル)',
  646.  
  647.             RENAME_BRACKETS_MOVETO_END_AND_ADD_DOJINSHI : '先頭括弧を後方送り',
  648.             RENAME_REMOVE_TRAILING_BRACKETS             : '末尾の括弧削除',
  649.             RENAME_NUMBERING_FORMAT                     : '第○○巻化',
  650.  
  651.             RENAME_AUTHOR_SWAP                          : '[]内のスワップ',   // [○○×△△]→[△△×○○]
  652.             RENAME_AUTHOR_CHOP                          : '[]内の末尾切り捨て', // [○○×△△×□□]→[○○×△△]
  653.             RENAME_AUTHOR_X_TO_BATSU                    : '[]内のxを×に変換',    // [○○x△△]→[○○×△△]
  654.  
  655.             RENAME_TO_ORIGINAL_NAME_BY_COMMENT          : '元の名前に戻す',
  656.             RENAME_FORMAT_TO_MAX_DIGITS_WITH_ZEROPADDING: '名前の数字部分の桁を揃える',
  657.            
  658.     // 移動系
  659.             MOVETO_NEWPACKAGE_WITH_FILENAME             : 'この名前を使用して新しいパッケージに移動',
  660.             MOVE_SAMENAME_PACKAGES_TO_PACKAGE           : '同じ名前のパッケージをまとめる',
  661.             ADD_LINKS_TO_LINKGRABBER                    : 'リンクグラバーへ再登録',
  662.             MOVE_LINK_TO_SAMENAME_PACKAGE               : 'リンクを同じ名前のパッケージに移動',
  663.            
  664.             SORT_PACKAGES_BY_TITLE_ASCENDING            : 'タイトルで並べ替え(昇順)',
  665.             SORT_PACKAGES_BY_TITLE_DESCENDING           : 'タイトルで並べ替え(降順)',
  666.             SORT_PACKAGES_BY_AUTHOR_ASCENDING           : '作者名で並べ替え(昇順)',
  667.             SORT_PACKAGES_BY_AUTHOR_DESCENDING          : '作者名で並べ替え(降順)',
  668.             SORT_PACKAGES_BY_ADDEDDATE_ASCENDING        : '追加日時で並べ替え(昇順)',
  669.             SORT_PACKAGES_BY_ADDEDDATE_DESCENDING       : '追加日時で並べ替え(降順)',
  670.            
  671.             DISABLE_LINKS_MYRULES_SINGLE                : '優先ホスト(単一)以外を無効',
  672.             DISABLE_LINKS_MYRULES_MULTIPLE              : '優先ホスト(複数)以外を無効',
  673.            
  674.             RENAME_ADD_TO_BEGIN                         : /^(.+) *>>$/,
  675.             RENAME_ADD_TO_END                           : /^<< *(.+)$/,
  676.             DISABLE_LINKS_BY_HOST                       : /^([-\da-zA-Z\.]+\.[\da-z]{2,4}) *を無効$/,
  677.             DISABLE_LINKS_BY_HOST_EXCLUDE               : /^([-\da-zA-Z\.]+\.[\da-z]{2,4}) *以外を無効$/,
  678.            
  679.             OPEN_LINK_BY_HOST                           : /^ブラウザで開く[- ]+(.+?) *のみ/,
  680.        
  681.             CONFIRM_OPEN_BROWSER_WITH_TOO_MANY_URLS:
  682.                 '%s1 個の検索用URLをブラウザで開こうとしています。\n本当に実行しますか?',
  683.             CONFIRM_OK:
  684.                 'OK',
  685.             CONFIRM_CANCEL:
  686.                 'キャンセル',
  687.             ADD_LINK_TO_LINKGRABBER_TIMEOUT:
  688.                 'リンクグラバーへの再登録でタイムアウトになりましたので\nこの処理を中断します',
  689.             ERROR_INVALID_TRIGGER_TYPE:
  690.                 'イベントトリガーのタイプが正しく設定されていません\n\n'+
  691.                 '解決方法:イベントスクリプトの設定から本スクリプトのトリガーに\n'+
  692.                 '     【ダウンロードリストコンテキストメニュー選択時】か、\n'+
  693.                 '     【リンクグラバーコンテキストメニュー選択時】を選択し、\n'+
  694.                 '     リストビュー画面の右クリックのコンテキストメニューから実行してください\n',
  695.             ERROR_INVALID_VALUE_IN_DISPATCHE_TABLE:
  696.                 'initDispatchTable()でのディスパッチデーブル内の値が不正です。\n値はfunction型でなければなりません',
  697.         },
  698.        
  699.         // Don't erase 'en'...
  700.         'en':
  701.         {
  702.             OPEN_EVERYTHING_BY_TITLE                    : 'EveryThing(Title)',
  703.             OPEN_EVERYTHING_BY_AUTHOR                   : 'EveryThing(Author)',
  704.             OPEN_LINK                                   : 'Open browser',
  705.            
  706.             OPEN_JD_CFG_FOLDER                          : 'Open JD Backup Folder',
  707.             OPEN_SOURCEURL                              : 'Open the Source Page',
  708.            
  709.     // リネーム系
  710.             RENAME_LINKS_BY_PACKAGENAME                 : 'Rename links to package name',
  711.             RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME : 'Rename package and links to this link name',
  712.  
  713.             RENAME_LINK_BY_LINKNAME                     : 'Rename links to this link name',     // 複数のリンクを選択してから右クリックし、右クリック直下のファイル名 (○○) [△△] □□.zip→(○○) [△△] □□を揃える
  714.             RENAME_LINK_BY_LINK_AUTHOR                  : 'Rename links to this link name(Author)', // (○○) [△△] □□.zip→[△△]部を揃える
  715.             RENAME_LINK_BY_LINK_TITLE                   : 'Rename links to this link name(Title)',  // (○○) [△△] □□ ~××~第01巻.zip→□□部を揃える
  716.             RENAME_LINK_BY_LINK_CATEGORY_AUTHOR         : 'Rename links to this link name(Category+Author)',
  717.             RENAME_LINK_BY_LINK_LONGTITLE               : 'Rename links to this link name(Category+Author+Title)',
  718.  
  719.             RENAME_BRACKETS_MOVETO_END_AND_ADD_DOJINSHI : 'Rename links to move starting brackets to end',
  720.             RENAME_REMOVE_TRAILING_BRACKETS             : 'Rename to removing trailed brackets',
  721.             RENAME_NUMBERING_FORMAT                     : 'Rename Numbering to comic format',
  722.  
  723.             RENAME_AUTHOR_SWAP                          : 'Rename Author - swap',   // [○○×△△]→[△△×○○]
  724.             RENAME_AUTHOR_CHOP                          : 'Rename Author - chop last author',   // [○○×△△×□□]→[○○×△△]
  725.             RENAME_AUTHOR_X_TO_BATSU                    : 'Rename Author - x to ×',    // [○○x△△]→[○○×△△]
  726.  
  727.             RENAME_TO_ORIGINAL_NAME_BY_COMMENT          : 'Rename links to original name by comment data',
  728.             RENAME_ALIGN_DIGITS_LENGTH                  : 'Rename links to align the digits length',
  729.            
  730.     // 移動系
  731.             MOVETO_NEWPACKAGE_WITH_NAME                 : 'Move to new package with this name',
  732.             MOVE_SAMENAME_PACKAGES_TO_PACKAGE           : 'Merge same packages',
  733.             ADD_LINKS_TO_LINKGRABBER                    : 'Add to LinkGrabber',
  734.             MOVE_LINK_TO_SAMENAME_PACKAGE               : 'Move links to same name package',
  735.            
  736.             SORT_PACKAGES_BY_TITLE_ASCENDING            : 'Sort packages by Title(ASC)',
  737.             SORT_PACKAGES_BY_TITLE_DESCENDING           : 'Sort packages by Title(DESC)',
  738.             SORT_PACKAGES_BY_AUTHOR_ASCENDING           : 'Sort packages by Author(ASC)',
  739.             SORT_PACKAGES_BY_AUTHOR_DESCENDING          : 'Sort packages by Author(DESC)',
  740.             SORT_PACKAGES_BY_ADDEDDATE_ASCENDING        : 'Sort packages by Added Date(ASC)',
  741.             SORT_PACKAGES_BY_ADDEDDATE_DESCENDING       : 'Sort packages by Added Date(DESC)',
  742.  
  743.             DISABLE_LINKS_MYRULES_SINGLE                : 'Diable links by my Rules (Single)',
  744.             DISABLE_LINKS_MYRULES_MULTIPLE              : 'Diable links by my Rules (Multiple)',
  745.  
  746.             RENAME_ADD_TO_BEGIN                         : /^(.+) *>>$/,
  747.             RENAME_ADD_TO_END                           : /^<< *(.+)$/,
  748.             DISABLE_LINKS_BY_HOST                       : /^Disable +([-\da-zA-Z\.]+\.[\da-z]{2,4})$/,
  749.             DISABLE_LINKS_BY_HOST_EXCLUDE               : /^Disable all but +([-\da-zA-Z\.]+\.[\da-z]{2,4})$/,
  750.             OPEN_LINK_BY_HOST                           : /^Open browser - only (.+)$/,
  751.  
  752.             CONFIRM_OPEN_BROWSER_WITH_TOO_MANY_URLS:
  753.                 'You are trying to open %s1 URLs in the browser.\nDo you want to allow execution?',
  754.             CONFIRM_OK:
  755.                 'OK',
  756.             CONFIRM_CANCEL:
  757.                 'Cancel',
  758.             ADD_LINK_TO_LINKGRABBER_TIMEOUT:
  759.                 'Adding link to Linkgrabber timed out,\nso this action has been aborted.',
  760.             ERROR_INVALID_TRIGGER_TYPE:
  761.                 'Invalid Event Trigger Type\n\n'+
  762.                 'Solution:Open Setting and "Event Script Setting", so find this script,\n'+
  763.                 '     choose trigger to "Select Download List Context Menu"\n'+
  764.                 '      Or "Select LinkGrabber Context Menu",\n'+
  765.                 '     Execute by Context Menu on the ListView\n',
  766.             ERROR_INVALID_VALUE_IN_DISPATCHE_TABLE:
  767.                 'Invalid value in the dispatch table at initDispatchTable()\nThe value must be a function.',
  768.         },
  769.     };
  770.     keys(r).forEach(function(k)
  771.     {
  772.         if (! (/^[a-z][-_a-z\d]*[a-z\d]$/i.test(k) || keys(r[k]).some(function(kk){return !(isRegExp(r[k][kk]) || isString(r[k][kk]))})) )
  773.             throw Error('Invalid value in the resource table at initResourceTables()\nThe value must be a string or RegExp.');
  774.     },this);
  775.    
  776.     Object.defineProperty(r, 'getResource', {enumerable: false, value: function(id, lang) {
  777.         const t = this.getTable(lang) || this.getTable(LANGUAGE_DEFAULT);
  778.         return t[id] || '';
  779.     }});
  780.     Object.defineProperty(r, 'hasLanguage', {enumerable: false, value: function(lang){return !!this[lang]}});
  781.     Object.defineProperty(r, 'getTable',    {enumerable: false, value: function(lang){return this[lang]}});
  782.     Object.defineProperty(r, 'getLanguages',{enumerable: false, value: function(){return keys(this)}});
  783.    
  784.     this.resource    = r;
  785. }
  786.  
  787. App.initDispatchTable = function()
  788. {
  789.     // DispatchTable (App.dispatcher = r) に登録する関数はAppのメソッド関数にする(App.* = function()~~)
  790.     // これらは this=App スコープ内で実行される
  791.     // 呼び出しは func.call(App, App.context.selection, App.name)
  792.     var r = {
  793.         // 名前は大文字英字と_のみ
  794.         OPEN_EVERYTHING_BY_TITLE                    : this.openEveryThingByTitle,
  795.         OPEN_EVERYTHING_BY_AUTHOR                   : this.openEveryThingByAuthorName,
  796.         OPEN_LINK                                   : this.openLink,
  797.         OPEN_JD_CFG_FOLDER                          : this.openJDCfgFolder,
  798.         OPEN_SOURCEURL                              : this.openSourceURL,
  799.         RENAME_LINKS_BY_PACKAGENAME                 : this.renameLinksByPackageName,
  800.         RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME : this.renamePackageAndLinksByContextLinkName,
  801.         RENAME_LINK_BY_LINKNAME                     : this.renameLinkByLinkName,        // 複数のリンクを選択してから右クリックし、右クリック直下のファイル名 (○○) [△△] □□.zip→(○○) [△△] □□を揃える
  802.         RENAME_LINK_BY_LINK_AUTHOR                  : this.renameLinkByLinkAuthor,  // (○○) [△△] □□.zip→[△△]部を揃える
  803.         RENAME_LINK_BY_LINK_TITLE                   : this.renameLinkByLinkTitle,   // (○○) [△△] □□ ~××~第01巻.zip→□□部を揃える
  804.         RENAME_LINK_BY_LINK_CATEGORY_AUTHOR         : this.renameLinkByLinkCategoryAuthor,
  805.         RENAME_LINK_BY_LINK_LONGTITLE               : this.renameLinkByLinkLongTitle,
  806.         RENAME_BRACKETS_MOVETO_END_AND_ADD_DOJINSHI : this.renameBracketsMoveToEndAndAddDojinshi,
  807.         RENAME_REMOVE_TRAILING_BRACKETS             : this.renameRemoveTrailingBrackets,
  808.         RENAME_NUMBERING_FORMAT                     : this.renameNumberingFormat,
  809.         RENAME_AUTHOR_SWAP                          : this.renameAuthorSwap,    // [○○×△△]→[△△×○○]
  810.         RENAME_AUTHOR_CHOP                          : this.renameAuthorChop,    // [○○×△△×□□]→[○○×△△]
  811.         RENAME_AUTHOR_X_TO_BATSU                    : this.renameAuthorXtoBatsu,    // [○○x△△]→[○○×△△]
  812.         RENAME_TO_ORIGINAL_NAME_BY_COMMENT          : this.renameToOriginalNameByComment,
  813.         RENAME_FORMAT_TO_MAX_DIGITS_WITH_ZEROPADDING: this.renameSequentialNumbersWithZeroPadding,
  814.         MOVETO_NEWPACKAGE_WITH_FILENAME             : this.moveToNewPackageWithFileName,
  815.         MOVE_SAMENAME_PACKAGES_TO_PACKAGE           : this.moveSameNamePackagesToPackage,
  816.         ADD_LINKS_TO_LINKGRABBER                    : this.addLinksToLinkGrabber,
  817.         MOVE_LINK_TO_SAMENAME_PACKAGE               : this.moveLinkToSameNamePackage,
  818.         SORT_PACKAGES_BY_TITLE_ASCENDING            : this.sortPackagesByTitleAscending,
  819.         SORT_PACKAGES_BY_TITLE_DESCENDING           : this.sortPackagesByTitleDescending,
  820.         SORT_PACKAGES_BY_AUTHOR_ASCENDING           : this.sortPackagesByAuthorAscending,
  821.         SORT_PACKAGES_BY_AUTHOR_DESCENDING          : this.sortPackagesByAuthorDescending,
  822.         SORT_PACKAGES_BY_ADDEDDATE_ASCENDING        : this.sortPackagesByAddedDateAscending,
  823.         SORT_PACKAGES_BY_ADDEDDATE_DESCENDING       : this.sortPackagesByAddedDateDescending,
  824.         DISABLE_LINKS_MYRULES_SINGLE                : this.disableLinksMyRules,
  825.         DISABLE_LINKS_MYRULES_MULTIPLE              : this.disableLinksMyRulesMultiple,
  826.         RENAME_ADD_TO_BEGIN                         : this.renameAddMenunameToBegin,
  827.         RENAME_ADD_TO_END                           : this.renameAddMenunameToEnd,
  828.         DISABLE_LINKS_BY_HOST                       : this.disableLinksByHost,
  829.         DISABLE_LINKS_BY_HOST_EXCLUDE               : this.disableLinksByHostExclude,
  830.         OPEN_LINK_BY_HOST                           : this.openLinkOnlyByHost,
  831.         SEARCH_ON_WEB                               : this.searchOnWEBSite,
  832.     };
  833.     keys(r).forEach(function(k){if (!isFunction(r[k])){throw Error(this.getResource('ERROR_INVALID_VALUE_IN_DISPATCHE_TABLE')+"\n\n"+k+"\n"+r[k])}},this);
  834.    
  835.     Object.defineProperty(r, 'getNames',    {enumerable: false, value:function(){return keys(this)}});
  836.     Object.defineProperty(r, 'getCallback', {enumerable: false, value:function(n){return this[n]}});
  837.     Object.defineProperty(r, 'hasName',     {enumerable: false, value:function(n){return !!this[n]}});
  838.    
  839.     this.dispatcher  = r;
  840. };
  841.  
  842. App.init = function()
  843. {
  844.     this.config.initConfig();
  845.  
  846.     this.menu = menu;
  847.     this.name = name.trim();
  848.     this.icon = icon;
  849.     this.shortCutString = shortCutString;
  850.    
  851.     this.DLC = {
  852.         selection:        null,
  853.         APIName:          'downloadsV2',
  854.         getAllPackages:   getAllFilePackages,
  855.         getAllLinks:      getAllDownloadLinks,
  856.         getPackageByUUID: getDownloadPackageByUUID,
  857.         getLinkByUUID:    getDownloadLinkByUUID,
  858.         moveLinks:        function(linkIds,afterLinkID,destPackageID){return callAPI(this.APIName,APIMethod.moveLinks,linkIds,afterLinkID,destPackageID)},
  859.         movePackages:     function(packageIds,afterDestPackageId){return callAPI(this.APIName,APIMethod.movePackages,packageIds,afterDestPackageId)},
  860.         movetoNewPackage: function(linkIds,pkgIds,newPkgName,downloadPath){return callAPI(this.APIName,APIMethod.movetoNewPackage,linkIds,pkgIds,newPkgName,downloadPath)},
  861.         queryLinks:       function(queryParams){return callAPI(this.APIName,APIMethod.queryLinks,queryParams)},
  862.     };
  863.     this.LGC = {
  864.         selection:        null,
  865.         APIName:          'linkgrabberv2',
  866.         getAllPackages:   getAllCrawledPackages,
  867.         getAllLinks:      getAllCrawledLinks,
  868.         getPackageByUUID: getCrawledPackageByUUID,
  869.         getLinkByUUID:    getCrawledLinkByUUID,
  870.         moveLinks:        function(linkIds,afterLinkID,destPackageID){return callAPI(this.APIName,APIMethod.moveLinks,linkIds,afterLinkID,destPackageID)},
  871.         movePackages:     function(packageIds,afterDestPackageId){return callAPI(this.APIName,APIMethod.movePackages,packageIds,afterDestPackageId)},
  872.         movetoNewPackage: function(linkIds,pkgIds,newPkgName,downloadPath){return callAPI(this.APIName,APIMethod.movetoNewPackage,linkIds,pkgIds,newPkgName,downloadPath)},
  873.         queryLinks:       function(queryParams){return callAPI(this.APIName,APIMethod.queryLinks,queryParams)},
  874.  
  875.         addLinks:         function(query){return callAPI(this.APIName,APIMethod.addLinks,query)},
  876.     };
  877.     if (this.isDLC())
  878.     {
  879.         this.DLC.selection = dlSelection;
  880.         this.context = this.DLC;
  881.     }
  882.     else if (this.isLGC())
  883.     {
  884.         this.LGC.selection = lgSelection;
  885.         this.context = this.LGC;
  886.     }
  887.     else
  888.     {
  889.         alert(this.menu);
  890.         throw Error();
  891.     }
  892.    
  893.     if (!Array.isArray(this.config.disableLinksMyRule))
  894.         this.config.disableLinksMyRule = [];
  895.    
  896.     // JD言語設定をリソーステーブルの使用言語に設定
  897.     // リソーステーブルの言語に無ければ、デフォルトの'en'にする
  898.     this.script_language = this.translation.language;
  899.     this.initResourceTables();
  900.     if (! this.resource.hasLanguage(this.script_language))
  901.         this.script_language = LANGUAGE_DEFAULT;
  902.     this.initDispatchTable();
  903. };
  904.  
  905. App.dispatch = function()
  906. {
  907.     const event_name = this.name;
  908.     const selection  = this.getSelection();
  909.     return this.resource.getLanguages().some(function(lng)
  910.     {
  911.         return this.dispatcher.getNames().some(function(n)
  912.         {
  913.             const translated_name = this.resource.getResource(n, lng);
  914.             if (isRegExp(translated_name))
  915.             {
  916.                 const r = translated_name.exec(event_name);
  917.                 if (!r) return false;
  918.                 (this.dispatcher[n]).call(this, selection, r[1]?r[1].trim():event_name);
  919.             }
  920.             else
  921.             {
  922.                 if (translated_name != event_name) return false;
  923.                 (this.dispatcher[n]).call(this, selection, event_name);
  924.             }
  925.             return true;
  926.         }, this);
  927.     }, this)
  928.         || (this.dispatcher['SEARCH_ON_WEB']).call(this, selection, event_name);
  929. };
  930.  
  931.  
  932. /**
  933.  * 汎用リネーム関数(1アイテム完結)
  934.  *     対象は選択されたリンク
  935.  *
  936.  * @param {(orig_name:string)=>string} callback_func 『リネーム前の名前』を受け取って
  937.  *    『リネーム後の名前』を返すコールバック関数
  938.  *     このコールバック関数は各リンク・パッケージのリネーム前に毎回呼び出される
  939.  * @param {object[]=getItemsByContextType(App.getSelection()} packages_or_links packageかlinkの配列
  940.  *      (指定されなければ、右クリックされたのがパッケージかリンクかで切り替える)
  941.  *
  942.  */
  943. App.genericRenamer = function(callback_func)
  944. {
  945.     const items = getItemsByContextType(this.getSelection());
  946.  
  947.     items.forEach(function(l)
  948.     {
  949.         /** @type {string} 元の名前*/
  950.         const orig_name = l.name;
  951.         /** @type {string} 新しい名前*/
  952.         const new_name = (callback_func).call(this, orig_name);
  953.         if (new_name && orig_name != new_name)
  954.             l.name = new_name;
  955.     },this);
  956. }
  957.  
  958. /**
  959.  * 先頭括弧内のカテゴリを置換する
  960.  *
  961.  * @param filename {string} 置換前の文字列
  962.  * @param cat {string} 置き換えるカテゴリ文字列
  963.  * @returns {string} 置換後の文字列
  964.  */
  965. function replaceCategory(filename, cat)
  966. {
  967.     if (!cat) return filename;
  968.  
  969. //  var regexp_category_and_author = /^ *\((?:一般|同人|成年|18禁|アニメ|BL|官能|少女|ライトノベル|書籍)[^\)]*\)(?: *\[[^\]]+\])? *$/;
  970.     var regexp_category = /^\((?:一般|同人|成年|18禁|アニメ|BL|官能|少女|ライトノベル|書籍)[^\)]*\) */;
  971.    
  972. //  if (!regexp_category_and_author.test(str) || !regexp_category.test(filename))
  973.     if (!regexp_category.test(cat) || !regexp_category.test(filename))
  974.         return cat.trim() + ' ' + filename;
  975.    
  976.     return filename.replace(regexp_category, cat.trim() + ' ');
  977. }
  978.  
  979. /**
  980.  *  ファイル名の先頭に指定文字列を追加
  981.  *     ※ (○○) がカテゴリ名っぽかったら、置換モード
  982.  *
  983.  *   メニュー名 "(一般小説) >>"
  984.  *   "[作者名] 作品名.zip" =>  "(一般小説) [作者名] 作品名.zip"
  985.  *
  986.  */
  987. App.renameAddMenunameToBegin = function(selection, event_name)
  988. {
  989.     if (! event_name) return;
  990.     this.genericRenamer(function(orig){return replaceCategory(orig, event_name)});
  991. }
  992.  
  993. /**
  994.  * ファイル名の末尾にメニュー名を追加
  995.  *    ※ファイル名内にメニュー名があればリネームしない
  996.  *
  997.  *  メニュー名 "<< 寄せ集め"
  998.  *   "[作者名] 作品名.zip" =>  "[作者名] 作品名 寄せ集め.zip"
  999.  *
  1000.  */
  1001. App.renameAddMenunameToEnd = function(selection, event_name)
  1002. {
  1003.     if (!event_name) return;
  1004.     this.genericRenamer(function(orig)
  1005.     {
  1006.         // 追加する文字列が既に含まれていればスキップ
  1007.         if (orig.indexOf(event_name) != -1) return '';
  1008.         return getFileSpec(orig) + ' ' + event_name + getExt(orig);
  1009.     });
  1010. }
  1011.  
  1012. App.renameRemoveTrailingBrackets = function(selection, event_name)
  1013. {
  1014.     this.genericRenamer(function(orig){
  1015.         const regexp_bottom_various_brackets = / *(?:\[.+?\]|\(.+?\)|【.+?)(.*?)$/;
  1016.         return orig_name.replace(regexp_bottom_various_brackets, "$1");
  1017.     });
  1018. }
  1019.  
  1020. /**
  1021.  * 先頭括弧を後方送り
  1022.  *
  1023.  *  同人誌用
  1024.  *  例)"(C100) [作者名] 作品名.zip" =>  "(同人誌) [作者名] 作品名(C100).zip"
  1025.  */
  1026. App.renameBracketsMoveToEndAndAddDojinshi = function(selection, event_name)
  1027. {
  1028.     this.genericRenamer(function(orig){
  1029.         const topbrackets_and_otherpart
  1030.             = /^(\([^\)]*\)) *(.+?)((?:\.part\d+|\.mp3|\.zip|\.rar|\.tar)?\.[0-9a-z]{2,6})?$/i;
  1031.         return replaceCategory(orig.replace(topbrackets_and_otherpart, "$2 $1$3"), '(同人誌)');
  1032.     });
  1033. }
  1034.  
  1035.  
  1036. function comicNumbering(str)
  1037. {
  1038.     return (isNaN(str)||str.length==2)?str:(str.length==1)?('0'+str):str.replace(/^0+(\d\d)$/,'$1');
  1039. }
  1040. function replaceNums(str)
  1041. {
  1042.     return str.replace(/\d+/g,function(x){return comicNumbering(x);})
  1043. }
  1044.  
  1045. /**
  1046.  * 第○○巻化
  1047.  *    v02→第02巻、v01-03→第01-03巻 へのリネーム
  1048.  *
  1049.  * 【右クリックされた項目】
  1050.  *   パッケージをクリックしていればパッケージ名を対象とする
  1051.  *   リンクをクリックしていればリンク名を対象とする
  1052.  *
  1053.  * 【選択されている項目】
  1054.  *   選択されているパッケージ、リンクの名前をリネーム対象とする
  1055.  */
  1056.  
  1057. App.renameNumberingFormat = function(selection, event_name)
  1058. {
  1059.     this.genericRenamer(function(orig){
  1060.         const anno = {w:' 透かし有り',s:' 寄せ集め',b:' 別スキャン',e:' (完)',A:' 単行本'};
  1061.         var dest = '';
  1062.         var res = [];
  1063.        
  1064.         // 既に「巻」がある
  1065.         if (res = /^(.+?)(第|全)?(\d{1,3}(?:[-+]\d{1,3})*巻.*)$/.exec(orig))
  1066.         {
  1067.             if (res[2]) return '';
  1068.            
  1069.             // 「第」抜け
  1070.             dest = res[1]+'第'+res[3];
  1071.         }   // 「話」
  1072.         else if (res = /^(.+?)[ _]ch?[-\.]?(\d{1,}(?:-\d{1,})?)(.*)$/i.exec(orig))
  1073.             dest = res[1] +' 第'+ replaceNums(res[2]) +'話'+ res[3];
  1074.         else
  1075.         {
  1076.             // "Lv." 誤認識を予め排除
  1077.             const reg_vol_num = /^(.+?[^Ll])(?:(?:v|[vV]ol(?:ume)?|VOL(?:UME)?)[ \.]? *)(\d{1,3}(?:[-+\.]\d{1,3})*)(e?A|[esbw]+)?\b(.*?)$/;
  1078.             const reg_numonly = /^(.+?) (\d{1,3}(?:[-+]\d{1,3})*)(e?A|[esbw]+)?( .+?)?$/;
  1079.            
  1080.             if ( (res = reg_vol_num.exec(orig)) || (res = reg_numonly.exec(orig)) )
  1081. //              ||  (res = /^(.+?) *(?:[vV](?:[oO][lL][ \.]? *)?)?(\d{1,3}(?:[-+\.]\d{1,3})*)(e?A|[esbw]+)?\b(.*)$/.exec(orig)) )
  1082.             {
  1083.                 dest = res[1] +' 第'+ replaceNums(res[2]) +'巻';
  1084.                 if (res[3] && (res[3] in anno))
  1085.                     dest += anno[res[3]];
  1086.                 if (res[4])
  1087.                     dest += res[4];
  1088.                 dest = dest.replace(/  +/g, ' ').trim();
  1089.             }
  1090.         }
  1091.         return dest;
  1092.     });
  1093. }
  1094.  
  1095. /**
  1096.  * []内のスワップ
  1097.  * [○○×△△]→[△△×○○]
  1098.  */
  1099. App.renameAuthorSwap = function(selection, event_name)
  1100. {
  1101.     this.genericRenamer(function(orig){return orig.replace(/\[(.+?)×([^×]+?)\]/, '[$2×$1]')});
  1102. }
  1103.  
  1104. /**
  1105.  * []内の末尾切り捨て
  1106.  * [○○×△△×□□]→[○○×△△]
  1107.  */
  1108. App.renameAuthorChop = function(selection, event_name)
  1109. {
  1110.     this.genericRenamer(function(orig){return orig.replace(/\[(.+?)×(?:[^×]+?)\]/, '[$1]')});
  1111. }
  1112.  
  1113. /**
  1114.  * []内のxを×に変換
  1115.  * [○○x△△]→[○○×△△]
  1116.  */
  1117. App.renameAuthorXtoBatsu = function(selection, event_name)
  1118. {
  1119.     this.genericRenamer(function(orig){return orig.replace(/\[([^×]+?)x([^×]+?)\]/, '[$1×$2]')});
  1120. }
  1121.  
  1122. /**
  1123.  * 名前の数字部分の桁を揃える
  1124.  *
  1125.  * 【右クリックされた項目】---
  1126.  * 【選択されている項目】選択されたリンクの名前の数字部分の桁を揃える
  1127.  *
  1128.  * ※ 最小桁数=2
  1129.  *
  1130.  */
  1131. App.renameSequentialNumbersWithZeroPadding = function(selection, event_name)
  1132. {
  1133. //  const min_length = 3;                                   // 最小桁数
  1134.     const min_length = 2;                                   // 最小桁数
  1135.    
  1136. //  const regexp_name = /^(\D*)0*(\d+?)(.*?)(\..+)?$/i;
  1137.     const regexp_name = /^(\D*)0*(\d+)(.*?)$/i;         // 数字有り文字列
  1138.  
  1139.     // 最大桁数を取得
  1140.     var max_length = min_length;
  1141.     selection.links.forEach(function (l)
  1142.     {
  1143.         const r = getFileSpec(l.name).match(regexp_name);
  1144.         if (r && (r[2].length > max_length))
  1145.             max_length = r[2].length;
  1146.     });
  1147.    
  1148.     this.genericRenamer(
  1149.         function(orig)
  1150.         {
  1151.             const r = getFileSpec(orig).match(regexp_name);
  1152.             if (!r) return null;
  1153.             return r[1] + zeroPadding(r[2], max_length) + r[3] + getExtFull(orig);
  1154.         }
  1155.     );
  1156. }
  1157.  
  1158.  
  1159. /**
  1160.  * 汎用リネーム関数(前処理でヒントを取得してのリネーム)
  1161.  *
  1162.  * @param {(contextItemName:string)=>string} funcGetHint
  1163.  *    右クリック直下のアイテムの名前が渡されるので、
  1164.  *    各リンク・パッケージをリネームする際に渡されるヒントを返す
  1165.  *    (右クリック直下のアイテムが無ければ、選択された一番上のアイテム)
  1166.  * @param {(orig_name:string, hint:string)=>string} funcRename
  1167.  *     各リンク・パッケージをリネームする毎に呼び出される(リネーム直前)
  1168.  *     各リンク・パッケージそれぞれの変更前の名前とfuncGetHintで
  1169.  *     返したヒントが渡されるので、変更後の新しい名前を返す
  1170.  */
  1171. App.genericRenamerWithHint = function(funcGetHint, funcRename)
  1172. {
  1173.     const sel = this.getSelection();
  1174.     var items=null;
  1175.     var contextItemName = '';
  1176.     var contextItemUUID = 0;
  1177.  
  1178. //  * 2024/01/20    希にsel.contextPackageが右クリック直下のパッケージではなく、
  1179. //                  最後に選択したパッケージor選択の一番下のパッケージになる場合がある。
  1180. //                  JDの再起動で直るが、スクリプトからでは判別不能のため代替措置は無理。
  1181.  
  1182.     if (sel.isPackageContext())
  1183.     {
  1184.         items = sel.packages;
  1185.         contextItemName = sel.contextPackage.name;
  1186.         contextItemUUID = sel.contextPackage.UUID;
  1187.     }
  1188.     else if (sel.isLinkContext())
  1189.     {
  1190.         items = sel.links;
  1191.         contextItemName = sel.contextLink.name;
  1192.         contextItemUUID = sel.contextLink.UUID;
  1193.     }
  1194.     if (! items || items.length <= 1) return;
  1195.     if (contextItemName == '') contextItemName = items[0].name;
  1196.    
  1197.     const hint = funcGetHint ? ((funcGetHint).call(this, contextItemName)) : contextItemName;
  1198.     if (!hint) return;
  1199.    
  1200.     items.forEach(function(l)
  1201.     {
  1202.         if (l.UUID == contextItemUUID) return;
  1203.        
  1204.         const orig_name = l.name;
  1205.         const new_name = funcRename ? ((funcRename).call(this, orig_name, hint)) : hint;
  1206.         if (new_name && orig_name != new_name)
  1207.             l.name = new_name;
  1208.     },this);
  1209. }
  1210.  
  1211.  
  1212. App.renameToOriginalNameByComment = function(selection, event_name)
  1213. {
  1214.     selection.links.forEach(function (l)
  1215.     {
  1216.         var r = T(l.getComment()).match(/(?:^|\|)orgfilename=([^\|]*)/);
  1217.         if (! r) return;
  1218.         l.name = r[1];
  1219.     });
  1220. }
  1221.  
  1222. /**
  1223.  * 名前を揃える
  1224.  *
  1225.  * 【クリックされたファイルの名前】で、【選択されているファイルの名前】を揃える
  1226.  *
  1227.  * パッケージ名同士を揃える必要はないし、パッケージを跨いで名前を揃える必要もないため
  1228.  * 同一パッケージ内のリンクのみ対象
  1229.  * 一つのパッケージ内でのみ有効
  1230.  */
  1231. App.renameLinkByLinkName = function(selection, event_name)
  1232. {
  1233.     if (selection.isPackageContext()) return;           // パッケージがクリックされているか
  1234.     if (1 != selection.packages.length) return;     // 2つ以上のパッケージが選択されているか
  1235.     if (! selection.packages[0].expanded) return;       // パッケージが閉じているか
  1236.    
  1237.     this.genericRenamerWithHint(null, null);
  1238. }
  1239.  
  1240.  
  1241. /**
  1242.  * ファイル名[作者名]を揃える
  1243.  *
  1244.  * 【クリックされたファイルの[]内】で、【選択されているファイルの[]内】を揃える
  1245.  *
  1246.  */
  1247. App.renameLinkByLinkAuthor = function(selection, event_name)
  1248. {
  1249. // ( ()先頭括弧 繰り返し ) ( [] 繰り返し)
  1250.     const regexp_name
  1251.         = /^((?:\([^\(\)]+\) *)*)((?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*)(.+?)$/i;
  1252.     this.genericRenamerWithHint(
  1253.         function(orig){const r=orig.match(regexp_name);return (r&&r[2])?r[2]:null},
  1254.         function(orig, hint){return orig.replace(regexp_name,function(m,m1,m2,m3){return (m1?(m1.trim()+' '):'')+hint.trim()+' '+m3.trim()})}
  1255.     );
  1256. }
  1257. /**
  1258.  * ファイル名(カテゴリ) [作者名]を揃える
  1259.  *
  1260.  * 【クリックされたファイルの(○○) [△△]】で、【選択されているファイルの(○○) [△△]】を揃える
  1261.  * 無ければ(○○) [△△]を追加する
  1262.  *
  1263.  */
  1264. App.renameLinkByLinkCategoryAuthor = function(selection, event_name)
  1265. {
  1266. // ( ()先頭括弧 繰り返し ) ( [] 繰り返し)
  1267.     const regexp_name
  1268.         = /^((?:\([^\(\)]+\) *)*)((?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*)(.+?)$/i;
  1269.     this.genericRenamerWithHint(
  1270.         function(orig){const r=orig.match(regexp_name);return (r&&r[1]&&r[2])?(r[1].trim()+' '+r[2].trim()):null},
  1271.         function(orig, hint){return orig.replace(regexp_name,function(m,m1,m2,m3){return hint+' '+m3.trim()})}
  1272.     );
  1273. }
  1274.  
  1275. /**
  1276.  * ファイル名 タイトルを揃える
  1277.  *
  1278.  * 【クリックされたファイルの(○○) [△△] □□の□□部分】で、【選択されているファイルの□□】を揃える
  1279.  *
  1280.  *
  1281.  * 巻数話数があるものだけ
  1282.  */
  1283. App.renameLinkByLinkTitle = function(selection, event_name)
  1284. {
  1285.     const regexp_name
  1286.         = /^((?:\([^\(\)]+\) *)*(?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*)(.+?) *((?:v(?:ol)?[-\._]?\d+|No[-\._]\d+|[第全]\d+|ch?[-\._]?\d+|[\(\[]| *透かし[あ有]り | *(?:雑誌)?寄せ集め| *別スキャン| *単行本| 20\d\d[-年]?\d+(?:-\d+)?月?号?| \d+).*)(?:\.part\d+\.rar|\.zip|\.[0-9a-z]{2,6})?$/i;
  1287.     this.genericRenamerWithHint(
  1288.         function(orig){const r=orig.match(regexp_name);return r?r[2]:null;},
  1289.         function(orig, hint){return orig.replace(regexp_name,function (m,m1,m2,m3){return m1.trim()+' '+hint.trim()+' '+m3.trim();});}
  1290.     );
  1291. }
  1292.  
  1293. /**
  1294.  * ファイル名 (カテゴリ)[作者名]タイトルまで揃える
  1295.  *
  1296.  * 【クリックされたファイルの(○○) [△△] □□】で、【選択されているファイル】を揃える
  1297.  *
  1298.  *
  1299.  * 巻数話数があるものだけ
  1300.  */
  1301. App.renameLinkByLinkLongTitle = function(selection, event_name)
  1302. {
  1303.     const regexp_name
  1304.         = /^((?:\([^\(\)]+\) *)*(?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*.+?) *((?:v(?:ol)?[-\._]?\d+|No[-\._]\d+|[第全]\d+|ch?[-\._]?\d+|[\(\[]| *透かし[あ有]り | *(?:雑誌)?寄せ集め| *別スキャン| *単行本| 20\d\d[-年]?\d+(?:-\d+)?月?号?| \d+).*)(?:\.part\d+\.rar|\.zip|\.[0-9a-z]{2,6})?$/i;
  1305.     this.genericRenamerWithHint(
  1306.         function(orig){const r=orig.match(regexp_name);return r?r[1]:null;},
  1307.         function(orig, hint){return orig.replace(regexp_name,function(m,m1,m2){return hint.trim()+' '+m2.trim()});}
  1308.     );
  1309. }
  1310.  
  1311. //
  1312. // [パッケージ内リネーム系]
  1313. //
  1314.  
  1315. /**
  1316.  * このリンクの名前でパッケージ内をリネーム
  1317.  *
  1318.  * 【右クリックされた項目】--- (複数パッケージを同時処理するため)
  1319.  * 【選択されている項目】
  1320.  *     『選択されたリンクのファイル名』で、パッケージ名と
  1321.  *     そのパッケージ内全リンクの名前を揃える。
  1322.  *
  1323.  *  ※ 安全のため、デフォルトパッケージ名に関わる『様々なファイル』『任意』が
  1324.  *     先頭に含まれた名前のパッケージはリネームしない
  1325.  *     リネームしたければパッケージ名を先に変更しておく
  1326.  *
  1327.  *  ※ 各パッケージ内で「リンクは一つだけ選択」する
  1328.  *     二つ以上のリンクが選択されたパッケージはリネームしない
  1329.  *     拡張子は維持
  1330.  */
  1331. App.renamePackageAndLinksByContextLinkName = function(selection, event_name)
  1332. {
  1333.     const defaultExtension = '.rar';
  1334.     const items = selection.links;
  1335.  
  1336.     // パッケージが重複するリンク+デフォルト名を弾くため
  1337.     // 弾くパッケージUUIDをbad_package_uiid_listに登録
  1338.     // 同一パッケージのリンクは配列内で隣接する
  1339.     var bad_package_uiid_list = {};
  1340.     var prev_puuid = 0;
  1341.     items.forEach(function(l)
  1342.     {
  1343.         var p = l.package;
  1344.         if (this.isDefaultPackageName(p.name)
  1345.             || p.UUID == prev_puuid)
  1346.             bad_package_uiid_list[p.UUID] = 1;
  1347.         prev_puuid = p.UUID;
  1348.     },this);
  1349.    
  1350.     items.forEach(function(l)
  1351.     {
  1352.         const p = l.package;
  1353.        
  1354.         // パッケージが重複するリンクorデフォルト名なら、次へ
  1355.         if (p.UUID in bad_package_uiid_list) return;
  1356.        
  1357.         // パッケージをリネーム
  1358.         const new_name = getFileSpec(l.name);
  1359.         if (! new_name) return;
  1360.         p.name = new_name;
  1361.        
  1362.         // デフォルト拡張子を設定
  1363.         // パッケージ内の先頭の拡張子をデフォルトにする
  1364.         // 無ければ.rar
  1365.         var default_ext = '';
  1366.         if (!p.downloadLinks.some(function(x){
  1367.             const e=getExt(x.name);
  1368.             return e && (default_ext=e);
  1369.         }))
  1370.             default_ext = defaultExtension;
  1371.  
  1372.         // パッケージ内の全リンクをリネーム
  1373.         p.downloadLinks.forEach(function(x)
  1374.         {
  1375.             //リネーム元リンクをスキップ
  1376.             if (x.UUID == l.UUID) return;
  1377.            
  1378.             // リンクをリネーム
  1379.             x.name = new_name + (getExtFull(x.name)||default_ext);
  1380.         });
  1381.     },this);
  1382. }
  1383.  
  1384. /**
  1385.  * パッケージ名で名前を揃える
  1386.  *
  1387.  * 【パッケージ名】で【パッケージ内全ファイルの名前】を揃える
  1388.  *  /^様々なファイル/ なパッケージではリネームしない
  1389.  *
  1390.  * 選択対象:リンクが選択された場合、親パッケージが対象になる
  1391.  *           右クリックは対象とはしない
  1392.  *
  1393.  * ※書庫ファイル以外がある場合はスキップ
  1394.  *
  1395.  */
  1396.  
  1397. App.renameLinksByPackageName = function(selection, event_name)
  1398. {
  1399.     const defaultExtension = '.rar';
  1400.     selection.packages.forEach(function(p)
  1401.     {
  1402.         // パッケージ名無しか、デフォルトパッケージ名ならスキップ
  1403. //      if ((! p.name) || isDefaultPackageName(p.name)) return;
  1404.         if ((! p.name) || this.isAutoCreatedPackageName(p.name)) return;
  1405.        
  1406.         var default_ext = '';
  1407.         if (p.downloadLinks.some(function(x){
  1408.             const e = getExt(x.name);
  1409.            
  1410.             // 連番画像ファイルなど、パッケージ名でリネームする事故防止
  1411.             if (e != '' && !/\.(?:zip|rar|cbr|cbz|7z|html?|epub)$/i.test(e))
  1412.                 return true;
  1413.  
  1414.             if (e && !default_ext) default_ext = e;
  1415.         },this))
  1416.         {
  1417. //          alert(DQ(p.name)+'\n\nに対するリネームの実行を中止しました\n\n【パッケージ名で名前を揃える】アクションは\n書庫ファイルに対してのみ実行可能です');
  1418.             return;
  1419.         }
  1420.         if (!default_ext) default_ext = defaultExtension;
  1421.        
  1422.         p.downloadLinks.forEach(function(l){l.name = p.name+(getExtFull(l.name)||default_ext)});
  1423.     },this);
  1424. }
  1425.  
  1426. /**
  1427.  * ファイル名から作者名またはタイトルを返す
  1428.  *
  1429.  * @param {string} filename ファイル名
  1430.  * @param {number} part 部分を指定する
  1431.  * @param {[string='|']} delim 該当するものが複数だった場合に、結合するための区切り文字
  1432.  * @returns {string} 指定された部分を切り出して返す
  1433.  */
  1434. function getKeywordFromFilename(filename, part, delim)
  1435. {
  1436.     var search_word = '';
  1437.     var r = filename.match(/^(?:(?:\([^\(\)]+?\) *)*\[([^\]]+)\] *)?((?:(?:COMIC|\u30B3\u30DF\u30C3\u30AF) *)?([a-z]+(?: [a-z]+?)*?(?:$|\.|(?= v\d+))|(?:[-+ a-z]+)+|[^ -]+))/i);
  1438.     if (r)
  1439.         if (part == gkPart.title
  1440.             || (r[1] && (r[1] == '\u30A2\u30F3\u30BD\u30ED\u30B8\u30FC'
  1441.             || r[1] == '\u96D1\u8A8C')))
  1442.             search_word = r[2].replace(/(?:[!?!?。、]+)$/,'');//検索ワードの末尾感嘆符などは不要
  1443.         else if (r[1])
  1444.         {
  1445.             if (part == gkPart.author)
  1446.                 search_word = r[1].split(/\u00D7|\uFF06|\uFF0F/).join((delim===undefined) ? '|' : delim);
  1447.             else if (part == gkPart.author_1st)
  1448.                 search_word = (r[1].split(/\u00D7|\uFF06|\uFF0F/))[0];
  1449.         }
  1450.     return(search_word.trim());
  1451. }
  1452.  
  1453. /**
  1454.  * ブラウザを開く個数が設定上限を超えているかどうか、
  1455.  *     超えていた場合、確認メッセージボックスを出して
  1456.  *     制限を超えて開くかどうかYES/NOの結果をboolで返す
  1457.  *
  1458.  * @param {number} url_count 上限数
  1459.  * @returns {boolean} true=YES/false=NO|cancel
  1460.  */
  1461. App.confirmAboutOpenBrowserCount = function(url_count)
  1462. {
  1463.     const max_open = this.config.open_browser_max;
  1464.     if (! url_count) return false;
  1465.     if (url_count <= max_open) return true;
  1466.    
  1467.     if (showConfirmDialog(
  1468.             jdFormatString(this.getResource('CONFIRM_OPEN_BROWSER_WITH_TOO_MANY_URLS'), [url_count]),
  1469.             this.getResource('CONFIRM_OK'),
  1470.             this.getResource('CONFIRM_CANCEL')))
  1471.         return true;
  1472.    
  1473.     return false;
  1474. }
  1475.  
  1476. App.internalOpenUrls = function(urls)
  1477. {
  1478.     const interval_time = this.config.open_browser_interval;
  1479.     if (! (Array.isArray(urls) && this.confirmAboutOpenBrowserCount(urls.length)))
  1480.         return;
  1481.     urls.forEach(function (url,i)
  1482.     {
  1483.         if (i) sleep(interval_time);
  1484.         callSync( this.config.path.browser, DQ(url) );
  1485.     },this);
  1486. }
  1487.  
  1488. /**
  1489.  * WEBブラウザで検索
  1490.  *
  1491.  * 選択されたリンクのファイル名から検索ワードを取得して、重複を取り除いた後、ブラウザで検索
  1492.  *
  1493.  *
  1494.  * ESL連想配列に
  1495.  * {"メニュー名":["URL(%REP%をキーワードに置換して引数として渡す)", position(gkPart.author|gkPart.title)]}
  1496.  *   メニュー名は、リストビューのコンテキストメニュー(右クリックメニュー)での名前
  1497.  *   URLは、"https://www.google.com/?q=%REP%"で、%REP%を検索ワードに置換してブラウザに渡す
  1498.  *   positionは、ファイル名から取得する検索ワードの位置。
  1499.  *     例)"(カテゴリ) [作者名] ワイのポエム -サブタイトルや- 第101巻.zip"
  1500.  *        gkPart.author:検索ワード="作者名"
  1501.  *        gkPart.title:検索ワード="ワイのポエム"
  1502.  */
  1503. App.searchOnWEBSite = function(selection, event_name)
  1504. {
  1505.     const search_table = this.config.web_search_table;
  1506.     if (! search_table[event_name]) return false;
  1507.    
  1508.     const parttype = search_table[event_name][0];
  1509.     const template_url = (search_table[event_name][1] || ('https://'+event_name+'/?s=%REP%'));
  1510.    
  1511.     var items = [];
  1512.    
  1513.     // 選択されたリンク.親が展開されているか?そのリンクから検索ワード:親の名前から検索ワード
  1514.     selection.links.forEach(function (l)
  1515.     {
  1516.         const n = l.package.expanded ? l.name :l.package.name;
  1517.         if (n == '' || this.isDefaultPackageName(n))
  1518.             return;
  1519.         var search_word = getKeywordFromFilename(n, parttype, ' ');
  1520.         if (parttype != gkPart.title && search_word == '')
  1521.             search_word = getKeywordFromFilename(n, gkPart.title);
  1522.        
  1523.         if (search_word != '') items.push(template_url.replace("%REP%", search_word));
  1524.     }, this);
  1525.  
  1526.     // 順番を維持して重複削除
  1527.     items = items.filter(function(v,i){return items.indexOf(v)===i});
  1528.     this.internalOpenUrls(items);
  1529.     return true;
  1530. }
  1531.  
  1532. /**
  1533.  * 指定リンク(l)のURLがホスト名パターンの配列(pat)にマッチするか否か
  1534.  *
  1535.  * @param {object} l リンク
  1536.  * @param {RegExp[]} pat ホスト名にマッチングさせる正規表現オブジェクトの配列
  1537.  * @return {boolean} true=マッチした、false=マッチしなかった
  1538.  */
  1539. function isLinkURLMatched(l,pat)
  1540. {
  1541.     return pat.some(function(m){return m.test(getLinkURL(l))})
  1542. }
  1543.  
  1544. /**
  1545.  * 指定パッケージ(pack)内のホスト名パターン(pattern)にマッチするリンクを有効/無効(enabled)にする
  1546.  *(マッチしないパッケージでは何もしない)
  1547.  *
  1548.  * @param {object} p パッケージ
  1549.  * @param {RegExp|RegExp[]} pattern ホスト名にマッチングさせる正規表現オブジェクトもしくはその配列
  1550.  * @param {boolean=true} enabled true=有効,false=無効
  1551.  * @return {boolean} true=マッチした、false=マッチしなかった
  1552.  */
  1553. App.setEnablePackageByPattern = function(p, pattern, enabled)
  1554. {
  1555.     const patList = Array.isArray(pattern)?pattern:[pattern];
  1556.     if (enabled===undefined) enabled = true;
  1557.    
  1558.     // デフォルトパッケージ名、パターンにマッチしないパッケージはスキップ
  1559.     if (this.isDefaultPackageName(p.name) || ! p.downloadLinks.some(function(l){return isLinkURLMatched(l, patList)}))
  1560.         return false;
  1561.    
  1562.     // パターンにマッチするか否かでリンクを有効/無効
  1563.     p.downloadLinks.forEach(function(l){l.setEnabled(isLinkURLMatched(l, patList) ? !!enabled : !enabled)});
  1564.     return true;
  1565. }
  1566.  
  1567. /**
  1568.  * 優先ホスト(単一)以外を無効化する
  1569.  *
  1570.  * 【右クリックされた項目】---
  1571.  * 【選択されている項目】選択パッケージ内のリンクをルールに合わせて有効/無効化する
  1572.  *
  1573.  */
  1574. App.disableLinksMyRules = function(selection, event_name)
  1575. {
  1576.     selection.packages.forEach(function(p) {    // パターンリスト上位からマッチング
  1577.         this.config.disableLinksMyRule.some(function(pat){return this.setEnablePackageByPattern(p, pat)},this)
  1578.     },this);
  1579. }
  1580.  
  1581. /**
  1582.  * 選択されたパッケージ内でホスト名パターン(pattern)にマッチするリンクを有効/無効(enabled)にする
  1583.  *(マッチしないパッケージでは何もしない)
  1584.  *
  1585.  * @param {RegExp|RegExp[]} pattern ホスト名にマッチングさせる正規表現オブジェクトもしくはその配列
  1586.  * @param {boolean=true} enabled true=有効,false=無効
  1587.  */
  1588. App.setEnableSelectedPackagesByPattern = function(selection, pattern, enabled)
  1589. {
  1590.     selection.packages.forEach(function(p){this.setEnablePackageByPattern(p, pattern, enabled)},this)
  1591. }
  1592.  
  1593. /**
  1594.  * 優先ホスト(複数)以外を無効化する
  1595.  *
  1596.  * 【右クリックされた項目】---
  1597.  * 【選択されている項目】選択パッケージ内のリンクをルールに合わせて有効/無効化する
  1598.  *
  1599.  */
  1600. App.disableLinksMyRulesMultiple = function(selection, event_name)
  1601. {
  1602.     this.setEnableSelectedPackagesByPattern(selection, this.config.disableLinksMyRule);
  1603. }
  1604.  
  1605. /**
  1606.  * 指定ホストを無効化する(hogehoge.comを無効化する)
  1607.  *
  1608.  * 【右クリックされた項目】---
  1609.  * 【選択されている項目】選択パッケージ内の指定したホスト名にマッチしたリンクを無効化する
  1610.  *
  1611.  */
  1612. App.disableLinksByHost = function(selection, event_name)
  1613. {
  1614.     this.setEnableSelectedPackagesByPattern(selection, new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/'), false);
  1615. }
  1616.  
  1617. /**
  1618.  * 指定ホスト以外を無効化する(hogehoge.com以外を無効化する)
  1619.  *
  1620.  * 【右クリックされた項目】---
  1621.  * 【選択されている項目】選択パッケージ内の指定したホスト名にマッチしたリンク以外を無効化する
  1622.  *
  1623.  */
  1624. App.disableLinksByHostExclude = function(selection, event_name)
  1625. {
  1626.     this.setEnableSelectedPackagesByPattern(selection, new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/'), true);
  1627. }
  1628.  
  1629.  
  1630. /**
  1631.  * ブラウザで開く
  1632.  *
  1633.  * 【右クリックされた項目】---
  1634.  * 【選択されている項目】リンクのcontentURLをブラウザで開く
  1635.  *
  1636.  * RegExp url_regexp = (NULL | 正規表現オブジェクト) マッチングするURLのみを開く
  1637.  */
  1638. App.openLink = function(selection, url_regexp)
  1639. {
  1640.     var items = selection.links.map(function(l){return getLinkURL(l)});
  1641.     if (isRegExp(url_regexp))
  1642.         items = items.filter(function(url){return url_regexp.test(url)});
  1643.     this.internalOpenUrls(items);
  1644. }
  1645.  
  1646.  
  1647. /**
  1648.  * 指定されたホストを含むリンクのみをブラウザで開く
  1649.  *
  1650.  * 【右クリックされた項目】---
  1651.  * 【選択されている項目】event_nameで指定されたホスト名をcontentURLに含むリンクのみブラウザで開く
  1652.  */
  1653. App.openLinkOnlyByHost = function(selection, event_name)
  1654. {
  1655.     this.openLink(selection, new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/'));
  1656. }
  1657.  
  1658. /**
  1659.  * 選択されたリンクの登録元ページをブラウザで開く
  1660.  *
  1661.  * 【右クリックされた項目】---
  1662.  * 【選択されている項目】リンクのoriginURLをブラウザで開く
  1663.  */
  1664. App.openSourceURL = function(selection, event_name)
  1665. {
  1666.     const items = {};
  1667.     selection.links.forEach(function(l){l.originURL&&(items[l.originURL]=1)});
  1668.     this.internalOpenUrls(Object.keys(items));
  1669. }
  1670.  
  1671.  
  1672.  
  1673. /**
  1674.  * EveryThingで検索(作者名)
  1675.  *
  1676.  * 【右クリックされた項目】パッケージorリンクの名前から最初の[]内の単語をEveryThingで検索
  1677.  * 【選択されている項目】---
  1678.  *
  1679.  * ※ 複数項目は不要
  1680.  */
  1681. App.openEveryThingByAuthorName = function(selection, event_name)
  1682. {
  1683.     this.openEveryThing(selection, gkPart.author)
  1684. }
  1685.  
  1686. /**
  1687.  * EveryThingで検索(タイトル)
  1688.  *
  1689.  * 【右クリックされた項目】パッケージorリンクの名前から最初の[]の後ろの単語をEveryThingで検索
  1690.  * 【選択されている項目】---
  1691.  */
  1692. App.openEveryThingByTitle = function(selection, event_name)
  1693. {
  1694.     this.openEveryThing(selection, gkPart.title)
  1695. }
  1696.  
  1697. App.openEveryThing = function(selection, part)
  1698. {
  1699.     const contextItem = getContextItemByContextType(selection);
  1700.     if (!contextItem) return;
  1701.    
  1702.     const search_word = getKeywordFromFilename(getFileSpec(contextItem.name), part||gkPart.author);
  1703.     if (!search_word) return;
  1704.    
  1705.     callSync( this.config.path.everything, '-s', DQ(search_word) );
  1706. }
  1707.  
  1708. /**
  1709.  * 同じ名前のパッケージをまとめる
  1710.  *
  1711.  *
  1712.  * 【右クリックされた項目】---
  1713.  *
  1714.  * 【選択されている項目】選択されているパッケージで同じ名前のものがあればまとめる
  1715.  */
  1716. App.moveSameNamePackagesToPackage = function(selection, event_name)
  1717. {
  1718.     var duptable = {};
  1719.     var hitnames = {};
  1720.     selection.packages.forEach(function(x)
  1721.     {
  1722.         if (x.name in duptable)
  1723.         {
  1724.             duptable[x.name].push(x.UUID);
  1725.             hitnames[x.name] = duptable[x.name];
  1726.         }
  1727.         else
  1728.             duptable[x.name] = [x.UUID];
  1729.     });
  1730.    
  1731.     for (var x in hitnames)
  1732.     {
  1733.         var link_uuids = [];
  1734.         const p_uuids = hitnames[x];
  1735.         for (var i=1; i < p_uuids.length; i++)
  1736.         {
  1737.             const p = (this.context.getPackageByUUID)(p_uuids[i]);
  1738.             if (!p) continue;
  1739.            
  1740.             link_uuids = link_uuids.concat( p.downloadLinks.map(function(l){return l.UUID;}) );
  1741.         }
  1742.        
  1743.         if (link_uuids.length)
  1744.             this.context.moveLinks(link_uuids, null, p_uuids[0]);
  1745.     }
  1746. }
  1747.  
  1748. /**
  1749.  * リンクを同じ名前のパッケージに移動(for turbobit)
  1750.  *
  1751.  * ※ その内、削除予定
  1752.  */
  1753. App.moveLinkToSameNamePackage = function(selection, event_name)
  1754. {
  1755.     const all_p = (this.context.getAllPackages)();
  1756.     selection.links.forEach(function(l)
  1757.     {
  1758.         var fname = getFileSpec(l.name).replace(/[_ ]/g,'');
  1759.         all_p.some(function(p)
  1760.         {
  1761.             if (fname == p.name.replace(/[_ ]/g,'').replace(/shit|rape/gi, ''))
  1762.             {
  1763.                 this.context.moveLinks([l.UUID], null, p.UUID);
  1764.                 return true;
  1765.             }
  1766. //          return true;
  1767.         });
  1768.     },this);
  1769. }
  1770.  
  1771.  
  1772. /**
  1773.  *
  1774.  * この名前を使用して新しいパッケージに移動
  1775.  *
  1776.  *
  1777.  * 【右クリックされた項目】
  1778.  *   名前/ダウンロードフォルダを新しいパッケージ名に使用
  1779.  *   取得できなければ何もしない
  1780.  *
  1781.  *   ※既存パッケージ名が使用された場合は、既存パッケージへの移動に切替
  1782.  *
  1783.  * 【選択されている項目】
  1784.  *   選択されているリンクを新しいパッケージに移動
  1785.  *   パッケージのみが選択されている項目はパッケージ内全リンクを移動
  1786.  *
  1787.  */
  1788. App.moveToNewPackageWithFileName = function(selection, event_name)
  1789. {
  1790.     const expand_after_newpackage = !!this.config.expand_after_newpackage;
  1791.     const waittime = this.config.waittime_for_expand_package;
  1792.    
  1793.     var newname = '';
  1794.     var dlpath = null;
  1795.     var new_p = null;
  1796.     var context_package_UUID = -1;          // 新規パッケージの位置用
  1797.     var prev_context_package_UUID = -1;     // 新規パッケージの位置用予備
  1798.    
  1799.     if (selection.isLinkContext())
  1800.     {
  1801.         newname = getFileSpec(selection.contextLink.name);
  1802.         dlpath = selection.contextLink.package.downloadFolder;
  1803.         context_package_UUID = selection.contextLink.package.UUID;
  1804.     }
  1805.     else if (selection.isPackageContext())
  1806.     {
  1807.         new_p = selection.contextPackage;
  1808.        
  1809.         newname = new_p.name;
  1810.         dlpath = new_p.downloadFolder;
  1811.         context_package_UUID = new_p.UUID;
  1812.     }
  1813.     if (newname == '') return;
  1814.  
  1815.     const all_p = (this.context.getAllPackages)();
  1816.     var prev = 0;
  1817.     if (all_p.some(function(p)
  1818.     {
  1819.         if (p.UUID == context_package_UUID)
  1820.             return true;
  1821.         prev = p.UUID;
  1822.     }))
  1823.         prev_context_package_UUID = prev;
  1824.    
  1825.     // 既存パッケージ名かどうかチェック、ES5なのでArray.prototype.findが無い
  1826.     if (! new_p)
  1827.     {
  1828.         all_p.some(function(p)
  1829.         {
  1830.             if (p.name == newname)
  1831.             {
  1832.                 new_p = p;
  1833.                 return true;
  1834.             }
  1835.         });
  1836.     }
  1837.  
  1838.     const items = selection.links.map(function(l){return l.UUID});
  1839.    
  1840.     if (new_p)
  1841.     {   // 既存パッケージ
  1842.         this.context.moveLinks(items, null, new_p.UUID);
  1843.         if (context_package_UUID == -1)
  1844.         {
  1845.             sleep(10);
  1846.             this.context.movePackages([new_p.UUID], context_package_UUID);
  1847.         }
  1848.        
  1849.         sleep(500);
  1850. //      new_p.setExpanded(expand_after_newpackage);
  1851.     }
  1852.     else
  1853.     {   // 新規パッケージ
  1854.         this.context.movetoNewPackage(items, null, newname, dlpath);
  1855.         sleep(waittime);// UIが反映されるのはPCスペック依存なので少し待機
  1856.         const l = (this.context.getLinkByUUID)(items[0]);
  1857.         this.context.movePackages([l.package.UUID], (this.context.getPackageByUUID)(context_package_UUID)?context_package_UUID:prev_context_package_UUID);
  1858.         sleep(300);
  1859. //      l.package.setExpanded(expand_after_newpackage);
  1860.     }
  1861. }
  1862.  
  1863.  
  1864. /////
  1865.  
  1866.  
  1867. /**
  1868.  * パッケージを並べ替える(画面上も)
  1869.  *
  1870.  * @param {(object,object)=>number} condition ソート関数に渡すコールバック関数
  1871.  */
  1872. App.sortPackages = function(condition)
  1873. {
  1874.     var packs = this.getSelection().packages;
  1875.     if (packs.length <= 1) packs = this.context.getAllPackages();
  1876.     if (packs.length <= 1) return;
  1877.    
  1878.     packs.sort(condition);
  1879.    
  1880.     for (var i=1; i<packs.length;++i)
  1881.         this.context.movePackages([packs[i].UUID], packs[i-1].UUID);
  1882. }
  1883.  
  1884. /**
  1885.  * 作者順に並べ替え
  1886.  *
  1887.  * sortPackagesByAuthor*
  1888.  */
  1889. App.sortPackagesByAuthorAscending  = function(){this.sortPackages(sortfunc_CompareAuthorA)}
  1890. App.sortPackagesByAuthorDescending = function(){this.sortPackages(sortfunc_CompareAuthorB)}
  1891. function getAuthorsString(s)
  1892. {
  1893.     const exp_getAuthor = /^(?:\([^\)]+\) *)*\[([^\]]+)\] .+$/;
  1894.     const r = s.match(exp_getAuthor);
  1895.     if (!r) return '';
  1896. //  return r[1].split(/[×&、,,]/).map(function(a){return a.trim()}).filter(function(a){return !!a}).sort().join('×');
  1897. //  return r[1].split(/[×&、,,]/, 2).map(function(a){return a.trim()}).filter(function(a){return !!a}).sort().join('×');
  1898.     return r[1].split(/[×&、,,]/, 2).map(function(a){return a.trim()}).filter(function(a){return !!a}).join('×');
  1899. }
  1900. function sortfunc_CompareAuthorA(a,b)
  1901. {
  1902.     const author_a = getAuthorsString(a.name);
  1903.     const author_b = getAuthorsString(b.name);
  1904.     return author_a.localeCompare(author_b, LANGUAGE_JA);   // 原則日本語ファイル名に対応
  1905. }
  1906. function sortfunc_CompareAuthorB(a,b){return sortfunc_CompareAuthorA(b,a)}
  1907.  
  1908. /**
  1909.  * タイトル順に並べ替え
  1910.  *
  1911.  * sortPackagesByTitle
  1912.  */
  1913. App.sortPackagesByTitleAscending  = function(){this.sortPackages(sortfunc_CompareTitleA)}
  1914. App.sortPackagesByTitleDescending = function(){this.sortPackages(sortfunc_CompareTitleB)}
  1915. function sortfunc_CompareTitleA(a,b)
  1916. {
  1917.     /** @type {RegExp} 先頭から続く(~~)[~~]を削除するパターン */
  1918.     const exp_getTitle = /^(?:\([^\)]+\) *|\[[^\]]+\] *)*/;
  1919.     const title_a = a.name.replace(exp_getTitle, '');
  1920.     const title_b = b.name.replace(exp_getTitle, '');
  1921.    
  1922.     return title_a.localeCompare(title_b, LANGUAGE_JA); // 原則日本語ファイル名に対応
  1923. }
  1924. function sortfunc_CompareTitleB(a,b){return sortfunc_CompareTitleA(b,a)}
  1925.  
  1926. /**
  1927.  * 追加日時順に並べ替え
  1928.  *
  1929.  * sortPackagesByAddedDate
  1930.  */
  1931. App.sortPackagesByAddedDateAscending  = function(){this.sortPackages(sortfunc_CompareAddedDateA)}
  1932. App.sortPackagesByAddedDateDescending = function(){this.sortPackages(sortfunc_CompareAddedDateB)}
  1933. function sortfunc_CompareAddedDateA(a,b){return a.getAddedDate()-b.getAddedDate()}
  1934. function sortfunc_CompareAddedDateB(b,a){return a.getAddedDate()-b.getAddedDate()}
  1935.  
  1936.  
  1937. /////
  1938.  
  1939.  
  1940. /**
  1941.  * リンクグラバーへ再登録
  1942.  *
  1943.  * 選択されているリンクを全てリンクグラバーに追加
  1944.  * URL、ファイル名、パッケージ名、パッケージのダウンロード先フォルダのみ設定
  1945.  *
  1946.  * 選択されているリンク(パッケージ選択時はパッケージ内全リンク)
  1947.  *
  1948.  * ※ リンクグラバーの登録は時間が掛かる処理であるため、処理時間が異常に長過ぎた場合、
  1949.  *    登録後のファイル名の設定をせずにスクリプトを終了
  1950.  */
  1951. App.addLinksToLinkGrabber = function(selection, event_name)
  1952. {
  1953.     const sellinks = selection.links;
  1954.     if (0 == sellinks.length) return;
  1955.  
  1956. //  alert(this.LGC);
  1957. //  alert(this.LGC.addLinks);
  1958.    
  1959.     var jobid_list = [];
  1960.     var url2jobid_table = {};
  1961.  
  1962.     sellinks.forEach(function(l)
  1963.     {
  1964.         //
  1965.         // addLinks時にファイル名を設定できないので、JOBID取得後、
  1966.         // queryLinksでリンクを取得してファイル名を設定する
  1967.         //
  1968.         const url = getLinkURL(l);
  1969.         if (!url) return;
  1970.         const jobid = this.LGC.addLinks(
  1971.         {
  1972.             links: url,
  1973.             packageName: l.package.name,
  1974.             overwritePackagizerRules:true,
  1975.             destinationFolder:l.package.downloadFolder,
  1976.             deepDecrypt:false,
  1977.             assignJobID:true
  1978.         });
  1979.         if (!jobid) return;
  1980.        
  1981.         jobid_list.push(jobid.id);
  1982.         url2jobid_table[url] = jobid.id;
  1983.     }, this);
  1984.    
  1985.     var link_count = sellinks.length;
  1986.     const limit_time = link_count * 2000 + 30000;
  1987.     const check_interval = 400;
  1988.     const start_time = (new Date()).getTime();
  1989.    
  1990.     while (1)
  1991.     {
  1992.         sleep(check_interval);
  1993.        
  1994.         //
  1995.         // addLinksは即完了しない。 queryLinksでjobUUIDsを指定すると
  1996.         // 完了しているJOBによって登録されたCrawledLinkのリストが返ってくる
  1997.         //
  1998.         var c_links = this.LGC.queryLinks(
  1999.         {
  2000.             jobUUIDs:jobid_list,
  2001.             availability:true,
  2002.             url:true,
  2003.             status:true,
  2004.             uuid:true
  2005.         });
  2006.        
  2007.         // 追加されたリンクのURLが同じかどうかで判定し、ファイル名を設定
  2008.         c_links.forEach(function(cl)
  2009.         {
  2010.             const cl_url = cl.url;
  2011.             const l = this.LGC.getLinkByUUID(cl.uuid);
  2012.             sellinks.some(function(sel_l)
  2013.             {
  2014.                 if (cl_url == sel_l.pluginURL
  2015.                     || cl_url == sel_l.contentURL)
  2016.                 {
  2017.                     l.name = sel_l.name;
  2018.                     return true;
  2019.                 }
  2020.             },this);
  2021.            
  2022.             // 登録完了したリンクのJobIDを、queryLinksに使うJobID Listから削除
  2023.             const jobid = url2jobid_table[cl_url];
  2024.             if (! jobid) return;
  2025.            
  2026.             jobid_list = jobid_list.filter(function(j){return j != jobid});
  2027.         }, this);
  2028.        
  2029.         // 完了判定
  2030.         link_count -= c_links.length;
  2031.         if (link_count <= 0 || 0 == jobid_list.length) break;
  2032.        
  2033.         // タイムオーバー判定
  2034.         //getCurrentTimeStamp()
  2035.         if (limit_time < ((new Date()).getTime() - start_time))
  2036.         {
  2037.             alert(this.getResource('ADD_LINK_TO_LINKGRABBER_TIMEOUT'));
  2038.             break;
  2039.         }
  2040.        
  2041.         // 残ったJobID ListのqueryLinksへ戻る
  2042.     }
  2043. }
  2044.  
  2045. App.openJDCfgFolder = function(selection, event_name)
  2046. {
  2047.     callSync( this.config.path.filer,  '"'+JD_HOME+'\\cfg"' );
  2048. }
  2049.  
  2050.  
  2051. ///// init
  2052. //try
  2053. {
  2054.     App.init();
  2055. }
  2056. //catch(e)
  2057. //{
  2058. //  throw Error(App.getResource('ERROR_INVALID_TRIGGER_TYPE'));
  2059. //  throw e;
  2060. //}
  2061. //alert("App.resource.getLanguages()="+App.resource.getLanguages()+"\n\n"+"App.dispatcher.getNames()=\n"+App.dispatcher.getNames().join('\n')+"\n");
  2062.  
  2063. App.dispatch();
  2064.  
  2065.  
  2066.  
  2067.  
  2068. ///// [exit script]
  2069.  
  2070.  
  2071.  
Add Comment
Please, Sign In to add comment