usamimi2323

JDownloader2 EventScript - 右クリックメニュー集 Ver 14.14

Apr 2nd, 2025 (edited)
24
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 148.79 KB | Source Code | 0 0
  1. /**
  2.  * JDownloader2 EventScript - 右クリックメニュー集
  3.  *
  4.  * 【概要】
  5.  * リネーム/検索/移動/並べ替えなどの右クリックメニューを追加するEvent Script
  6.  *
  7.  * 実装済み:
  8.  * ・リネーム各種
  9.  * ・ローカルファイル検索(Everythingを呼び出す)
  10.  * ・WEB検索(ブラウザを呼び出す)
  11.  * ・特定ホストだけを有効/無効
  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.  * UserConfig内を自分の環境に合わせて書き換える
  36.  *
  37.  * UserConfig.path内のパスを書き換える
  38.  * UserConfig.SEARCH_WEB内の設定を自分用に書き換える
  39.  * USerConfig.menuConfig.enableOnlyHost
  40.  * USerConfig.menuConfig.disableHost
  41.  *
  42.  * 【使い方】
  43.  *
  44.  * ----------------------
  45.  * (0) 前提
  46.  * ----------------------
  47.  *   JDownloader2 に「イベントスクリプト拡張モジュール」をインストール(必須)
  48.  *
  49.  * ----------------------
  50.  * (1) スクリプトの登録
  51.  * ----------------------
  52.  *   「設定」→「イベントスクリプト」→「+追加」ボタンから新しいスクリプトを追加
  53.  *
  54.  *   チェックボックスをチェックし有効化、トリガーには
  55.  *    『ダウンロードリストコンテキストメニュー選択時』
  56.  *    『リンクグラバーリストコンテキストメニュー選択時』
  57.  *   どちらかを選択する(スクリプトの名前は任意)
  58.  *
  59.  *   「編集」ボタンを押し、スクリプトエディタ画面を表示
  60.  *
  61.  *   このスクリプトファイル全体をコピー&ペーストし「保存」ボタンを押し保存する。
  62.  *
  63.  *   ※もう一度繰り返し、ダウンロードリストとリンクグラバーの両方ともトリガーされるようにする
  64.  *
  65.  * ----------------------
  66.  * (2) メニュー項目の追加
  67.  * ----------------------
  68.  *   保存した後に再度、「編集」ボタンからスクリプトエディタ画面を表示する
  69.  *
  70.  *   ・自動でメニュー項目を追加したい場合
  71.  *
  72.  *       「試行」ボタン(TestRun)を押すとメニュー項目の自動インストールモードに移行、
  73.  *       メッセージに従ってインストールする
  74.  *
  75.  *   ・手動でメニュー項目を追加したい場合
  76.  *
  77.  *       「右クリックメニュー:*****」ボタンを押して「メニュー管理画面」を開く、
  78.  *       「+ボタン」またはコンテキストメニューから「アクション追加」
  79.  *       →「EventTrigger」→プロパティの名前を追加したいアクションの「メニュー名」にする
  80.  *       ……これを追加したいメニュー項目分繰り返す
  81.  *
  82.  *   ※これらのメニュー項目は後でメニュー管理画面から編集可能。
  83.  *
  84.  *
  85.  *
  86.  * -----------------------------------------------------------------------------------------------------------
  87.  * メニュー名                                 説明
  88.  * -----------------------------------------------------------------------------------------------------------
  89.  * Everythingで検索(タイトル)                 Everythingで検索する((△△) [○○] □□ の□□を検索ワードに)
  90.  * Everythingで検索(作者名)                   Everythingで検索する((△△) [○○] □□ の○○を検索ワードに)
  91.  * ブラウザで開く                             設定したブラウザで選択されているリンクを開く
  92.  *
  93.  * JDバックアップフォルダを開く
  94.  * 登録元ページを開く                         登録元(リンクにsourceUrlがあれば)を開く
  95.  *
  96.  * パッケージ名で名前を揃える                 パッケージ名でパッケージ内のリンクの名前を揃える
  97.  *
  98.  * このリンクの名前でパッケージ内を全て揃える 選択しているリンクの名前で、パッケージとパッケージ内のリンクの名前を揃える
  99.  *                                            (各パッケージ内でリンクを一つだけ選択)
  100.  *
  101.  * 名前を揃える                               右クリックされたリンク(もしくは選択された一番上のリンクの名前)で
  102.  *                                            選択されているリンクの名前を揃える※1
  103.  *                                            
  104.  * 名前を揃える(作者名)                       選択などは※1と同様 (○○) [△△] □□ の[△△]を揃える
  105.  * 名前を揃える(タイトル)                     選択などは※1と同様 (○○) [△△] □□ の□□を揃える
  106.  * 名前を揃える(カテゴリ+作者名)              選択などは※1と同様 (○○) [△△] □□ の(○○) [△△]を揃える
  107.  * 名前を揃える(カテゴリ+作者名+タイトル)     選択などは※1と同様 (○○) [△△] □□ 第01-04巻の(○○) [△△] □□を揃える
  108.  *
  109.  * 第○○巻化                                 v01を第01巻にリネーム
  110.  *
  111.  * []内のスワップ                             [○○×△△]→[△△×○○]
  112.  * []内の末尾切り捨て                         [○○×△△×□□]→[○○×△△]
  113.  * []内のxを×に変換                          [○○x△△]→[○○×△△]
  114.  *
  115.  * 名前の数字の桁揃え                         名前の数字の桁を揃える(0埋め、最小桁数=2)
  116.  * 名前の全角英数字を半角に変換
  117.  * 名前の正規化
  118.  * 名前のURLデコード
  119.  * 先頭括弧を後方送り
  120.  * 末尾の括弧削除
  121.  *
  122.  * 名前を元に戻す(パッケージャ適用後)...      パッケージャ適用直後の名前に戻す ※2
  123.  * 名前を元に戻す...                          パッケージャ適用前の名前に戻す  ※2
  124.  *
  125.  *                                            ※2 リンクのプロパティに該当する名前を設定しておく必要がある
  126.  *                                               別途「名前を元に戻す用」EventScript の導入が必須
  127.  *
  128.  * ○○ >>                                    「【追加したい文字】   >>」
  129.  *                                            リンクまたはパッケージの名前の先頭に○○を追加する
  130.  *                                            (○○) でカテゴリ名の場合は、追加ではなく置換する
  131.  *
  132.  * << ××                                    「<<   【追加したい文字】」
  133.  *                                            リンクまたはパッケージの名前の終端に××を追加する
  134.  *
  135.  * この名前で新しいパッケージに移動           右クリックされたリンクまたはパッケージの名前を使用し
  136.  *                                            選択されたリンクまたはパッケージを
  137.  *                                            新しいパッケージに移動し、まとめる
  138.  *                                            
  139.  * リンクグラバーへ再登録                     選択されたリンクまたはパッケージをリンクグラバーに再登録する
  140.  *
  141.  * タイトルで並べ替え(昇順)                   (△△) [○○] □□……選択されたパッケージを□□で昇順並べ替え
  142.  * タイトルで並べ替え(降順)                   (△△) [○○] □□……選択されたパッケージを□□で降順並べ替え
  143.  * 作者名で並べ替え(昇順)                     (△△) [○○] □□……選択されたパッケージを[○○]で昇順並べ替え
  144.  * 作者名で並べ替え(降順)                     (△△) [○○] □□……選択されたパッケージを[○○]で降順並べ替え
  145.  * 追加日時で並べ替え(昇順)                   選択されたパッケージを追加日時で昇順並べ替え
  146.  * 追加日時で並べ替え(降順)                   選択されたパッケージを追加日時で降順並べ替え
  147.  * カテゴリ別タイトルで並べ替え(昇順)
  148.  * カテゴリ別タイトルで並べ替え(降順)
  149.  * カテゴリ別作者名で並べ替え(昇順)
  150.  * カテゴリ別作者名で並べ替え(降順)
  151.  * 優先ホストで並べ替え(昇順)
  152.  * 優先ホストで並べ替え(降順)
  153.  *
  154.  * 優先ホスト(単一)のみ有効                   選択しているパッケージまたはリンクのURLを
  155.  *                                            優先順のマッチングリストの先頭から順にマッチングしていき、
  156.  *                                            該当したら、それ以外のパッケージ内のリンクを無効にする
  157.  *                                            (UserConfig.priorityHostRule で設定可能)
  158.  * 優先ホスト(複数)のみ有効                   選択しているパッケージまたはリンクのURLが
  159.  *                                            マッチングリストにマッチするか否かで、パッケージ内のリンクを有効/無効にする
  160.  *                                            (UserConfig.priorityHostRule で設定可能)
  161.  *
  162.  * hogehoge.com のみ有効                       選択されたリンクからホスト名hogehoge.comを含まないリンクを無効にする
  163.  * hogehoge.com を無効                         選択されたリンクからホスト名hogehoge.comを含んだリンクを無効にする
  164.  *                                            ※hogehoge.comには任意のホスト名を指定可能
  165.  *
  166.  * hogehoge.com のみ開く                      選択されたリンクからホスト名hogehoge.comを含んだリンクのみをブラウザで開く
  167.  *                                            ※hogehoge.comには任意のホスト名を指定可能
  168.  * -----------------------------------------------------------------------------------------------------------
  169.  *
  170.  *
  171.  * 【ローカルファイルへのアクセス】
  172.  *
  173.  *  実行時に以下のローカルファイルにアクセスする
  174.  *
  175.  *  [読み込み(readFile())]
  176.  *  ・JD_HOME + '\cfg\language.json'
  177.  *  ・JD_HOME + '\translations\org\jdownloader\gui\translate\GuiTranslation.*.lng'
  178.  *  ・JD_HOME + '\translations\org\jdownloader\translate\JdownloaderTranslation.*.lng'
  179.  *
  180.  *  [書き込み/読み込み(writeFile()/readFile())]
  181.  *  ・JD_HOME + '\cfg\backup_*.jdDLMenu'
  182.  *  ・JD_HOME + '\cfg\backup_*.jdLGMenu'
  183.  *
  184.  *  [実行(callSync()/callAsync())]
  185.  *  ・指定されたeverythingの実行ファイル
  186.  *  ・指定されたbrowserの実行ファイル
  187.  *  ・指定されたファイラーの実行ファイル
  188.  *
  189.  * 【トリガー】
  190.  *
  191.  *    『ダウンロードリストコンテキストメニュー選択時』
  192.  *    『リンクグラバーリストコンテキストメニュー選択時』
  193.  *
  194.  *    このスクリプトで両方のトリガータイプに対応
  195.  *
  196.  *
  197.  *
  198.  *
  199.  * EventScriptはrequire()を使用せず、単一ファイル
  200.  * (requireするとlogファイルにその都度、スクリプトの内容が全て書き込まれるため
  201.  * logファイルのサイズが大きくなる)
  202.  *
  203.  **/
  204.  
  205. 'use strict';
  206. const SIGNATURE = 'JD2NewsoxMenus';
  207. const VERSION = '14.14';
  208.  
  209. /** @enum {number} */
  210. const findBy={
  211.     author: 1,
  212.     author_1st: 2,
  213.     title: 3
  214. };
  215.  
  216. const UserConfig =
  217. {
  218. // 【設定ここから】
  219.  
  220.     path:
  221.     {
  222.         everything : 'C:\\Program Files\\Everything\\Everything.exe',
  223.         browser    : 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
  224.         filer      : 'C:\\windows\\explorer.exe',
  225. //      filer      : getEnv('WINDIR')+'\\explorer.exe',
  226.     },
  227.    
  228.     SEARCH_WEB:
  229.     {
  230.     //----------------------------------------------------------------
  231.     //
  232.     // 書式 '${name}': [${part},'${URL}'],
  233.     //
  234.     // ${name}  メニュー名
  235.     // ${part}  ファイル名から検索ワードに使う部分を指定する
  236.     //          (findBy.title|findBy.author|findBy.author_1st) ※1
  237.     // ${URL}   文字列 %REP% が検索ワードに置換されたURLがブラウザに渡される
  238.     //          (デフォルト値) null => "https://${name}/?s=%REP%"
  239.     //
  240.     // ※1
  241.     //
  242.     // 【例】(一般小説) [作者A×作者B] タイトル ~サブタイトル~ 第01巻.zip
  243.     //
  244.     //   findBy.title    タイトル部分       【例】検索ワード='タイトル'
  245.     //   findBy.author   []内の作者名部分列挙   【例】検索ワード='作者A OR 作者B'
  246.     //   findBy.author_1st []内の作者名部分先頭のみ 【例】検索ワード='作者A'
  247.     //
  248.     //----------------------------------------------------------------
  249.         //*****[ 自分用に書き換える ]*****/
  250. //      'x3**.net'          : [findBy.author_1st,   null],
  251. //      'bs***.com'         : [findBy.author_1st,   null],
  252. //      '*****.se'          : [findBy.title,        'https://*****.se/search/%REP%/'],
  253. //      '888**.**'          : [findBy.author_1st,   'https://888**.**/search.php?q=%REP%'],
  254. //      '******nexus.com'   : [findBy.author_1st,   'https://******nexus.com/public/search.php?x=12&y=17&q=%REP%'],
  255. //      '678**.***'         : [findBy.author_1st,   null],
  256. //      '*****77.***'       : [findBy.author_1st,   null],
  257. //      'dl-***.***'        : [findBy.author_1st,   null],
  258. //      'raw*****.cc'       : [findBy.author_1st,   null],
  259. //      '*****-zone.***'    : [findBy.author_1st,   'http://www.*****-zone.***/?submit=Search&s=%REP%'],
  260. //      '******core.***'    : [findBy.author_1st,   null],
  261. //      '******omg.***'     : [findBy.author_1st,   null],
  262. //      'n******.net'       : [findBy.author_1st,   'https://n******.net/search/?q=%REP%'],
  263.        
  264.         // 検索
  265.         'SEARCH_GOOGLE_AUTHOR'  : [findBy.author,   'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  266.         'SEARCH_GOOGLE_TITLE'   : [findBy.title,    'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  267. //      'google.com (作者)'       : [findBy.author,   'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  268. //      'google.com (タイトル)' : [findBy.title,    'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  269. //      'google.com (Author)'   : [findBy.author,   'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  270. //      'google.com (Title)'    : [findBy.title,    'https://www.google.com/search?q=%REP%&ie=utf-8&oe=utf-8'],
  271.     },
  272.    
  273.     // 「優先ホスト(○○)のみ有効」で使用するルール
  274.     // RegExp(正規表現)オブジェクト配列
  275.     // ホスト名にマッチングさせる
  276.     priorityHostRule:       [
  277.                                 /hexload\.com/,         // hCaptcha
  278.                                 /dailyuploads\.net/,    // reCaptchaが必須になった、クールタイムが極小、制限少ない
  279.                                 /mexa\.sh/,
  280.                                 /katfile\.com/,
  281. //                              /rapidgator\.net/,
  282. //                              /fikper\.com/,
  283. //                              /frdl\.(?:is|io|to)/,   // 時折、不安定。reCaptchaが必要になったり、エラー出たり
  284.                             ],
  285.  
  286.     OPEN_BROWSER_INTERVAL: 2000,        // 複数のURLをブラウザで開く時の間隔(ミリ秒)
  287.     OPEN_BROWSER_MAX: 10,               // 複数のURLをブラウザで開く時の上限数
  288.    
  289.     WAITTIME_FOR_EXPAND_PACKAGE:100,    // パッケージ/リンク移動後にツリー展開するまでの待機時間(ミリ秒)
  290.     EXPAND_AFTER_NEWPACKAGE: false,     // パッケージに移動後にツリー展開したくない場合はfalseにする
  291.     DEFAULT_EXTENSION: '.rar',          // 名前を揃えるなどの時に使うデフォルトの拡張子
  292.     RENAME_ALIGN_FILEEXTENSION: [       // 特定のリネームで対応する拡張子
  293.         'rar',
  294.         'zip',
  295.         '7z',
  296.         'cbz',
  297.         'cbr',
  298.         'pdf',
  299.         'epub',
  300.         'html?',
  301.         'azw3',
  302.         'mp4',
  303.         'mkv',
  304.         'webm',
  305.         'avi',
  306.         'wmv',
  307.     ],
  308.  
  309.     //
  310.     // ※以下の設定は、メニューアイテムインストールモードの時のみ使用
  311.     //
  312.     menuConfig: {
  313.         language:null,
  314.         // メニュー項目「<< ○○」の○○を初期設定
  315.         RENAME_ADD_TO_END:[
  316.             "別スキャン",
  317.             "単行本",
  318.             "透かし有り",
  319.             "寄せ集め",
  320.             "epub",
  321.         ],
  322.         // メニュー項目「○○ >>」の○○を初期設定
  323.         RENAME_ADD_TO_BEGIN:[
  324.             "(一般コミック)",
  325.             "(一般コミック・少女)",
  326.             "(一般コミック) [雑誌]",
  327.             "(一般小説)",
  328.             "(一般書籍)",
  329.             "(成年コミック)",
  330.             "(成年コミック) [雑誌]",
  331.             "(同人誌)",
  332.             "(同人CG集)",
  333.             "(18禁アニメ)",
  334.         ],
  335.         // メニュー項目「○○のみ有効」の○○のホスト名を初期設定
  336.         ENABLE_LINKS_BY_HOST_ONLY:[
  337.             "dailyuploads.net",
  338.             "hexload.com",
  339.             "mexa.sh",
  340.             "katfile.com",
  341.             "rapidgator.net",
  342.         ],
  343.         // メニュー項目「○○を無効」の○○のホスト名を初期設定
  344.         DISABLE_LINKS_BY_HOST:[
  345.             "frdl.is",
  346.             "filespayouts.com",
  347.             "filepv.com",
  348.             "rosefile.net",
  349.             "takefile.link",
  350.         ],
  351.         // メニュー項目「○○のみ開く」の○○のホスト名を初期設定
  352.         OPEN_LINKS_BY_HOST:[
  353.             "frdl.is",
  354.             "filespayouts.com",
  355.             "filepv.com",
  356.             "rosefile.net",
  357.         ],
  358.  
  359.         //
  360.         // 右クリックメニューに追加するメニューのテンプレート
  361.         // メニューアイテムのオブジェクト構造
  362.         // 足りないプロパティは自動補完してメニューアイテムを構築
  363.         //
  364.         // サブメニュー(CONTAINER)はitemsに配列をセット
  365.         // セパレータはname:"-"
  366.         //
  367.         // 既存の各メニューアイテムも挿入可
  368.         //
  369.         templateMenuItems:
  370.         [
  371.             {"name":"-"},
  372.             {
  373.                 "name":"SUBMENU_RENAME_ALIGN",
  374.                 "iconKey":"edit",
  375.                 "items":[
  376.                     {"name":"RENAME_LINKS_BY_PACKAGENAME","iconKey":"edit"},
  377.                     {"name":"RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME","iconKey":"edit"},
  378.                     {"name":"-"},
  379.                     {"name":"RENAME_LINK_BY_LINKNAME","iconKey":"edit"},
  380.                     {"name":"RENAME_LINK_BY_LINK_AUTHOR","iconKey":"edit"},
  381.                     {"name":"RENAME_LINK_BY_LINK_TITLE","iconKey":"edit"},
  382.                     {"name":"RENAME_LINK_BY_LINK_CATEGORY_AUTHOR","iconKey":"edit"},
  383.                     {"name":"RENAME_LINK_BY_LINK_LONGTITLE","iconKey":"edit"},
  384.                     {"name":"-"},
  385.                     {"name":"RENAME_AUTHOR_SWAP","iconKey":"edit"},
  386.                     {"name":"RENAME_AUTHOR_CHOP","iconKey":"edit"},
  387.                     {"name":"RENAME_AUTHOR_X_TO_BATSU","iconKey":"edit"},
  388.                     {"name":"-"},
  389.                     {"name":"RENAME_NUMBERING_FORMAT","iconKey":"edit"},
  390.                     {"name":"RENAME_ALIGN_DIGITS_LENGTH","iconKey":"edit"},
  391.                     {"name":"RENAME_FULLWIDTH_TO_HALFWIDTH","iconKey":"edit"},
  392.                     {"name":"RENAME_NORMALIZE","iconKey":"edit"},
  393.                     {"name":"RENAME_URLDECODE","iconKey":"edit"},
  394.                     {"name":"-"},
  395.                     {"name":"RENAME_STARTING_BRACKETS_MOVETO_END","iconKey":"edit"},
  396.                     {"name":"RENAME_REMOVE_TRAILING_BRACKETS","iconKey":"edit"},
  397.                     {"name":"-"},
  398.                     {"name":"RENAME_TO_PACKAGIZER_NAME","iconKey":"reset"},
  399.                     {"name":"RENAME_TO_ORIGIN_NAME","iconKey":"reset"},
  400.                 ]
  401.             },
  402.            
  403.             {
  404.                 "name":"SUBMENU_RENAME_ADD_REPLACE",
  405.                 "iconKey":"edit",
  406.                 "items":[
  407.                     {"name":"INSERTMENU.RENAME_ADD_TO_END","iconKey":"edit"},
  408.                     {"name":"-"},
  409.                     {"name":"INSERTMENU.RENAME_ADD_TO_BEGIN","iconKey":"edit"},
  410.                    
  411. //                  {"name":"<< 別スキャン"},
  412. //                  {"name":"<< 単行本"},
  413. //                  {"name":"<< 透かし有り"},
  414. //                  {"name":"<< 寄せ集め"},
  415. //                  {"name":"-"},
  416. //                  {"name":"(一般コミック) >>"},
  417. //                  {"name":"(一般コミック・少女) >>"},
  418. //                  {"name":"(一般コミック) [雑誌] >>"},
  419. //                  {"name":"(一般小説) >>"},
  420. //                  {"name":"(一般書籍) >>"},
  421. //                  {"name":"(成年コミック) >>"},
  422. //                  {"name":"(成年コミック) [雑誌] >>"},
  423. //                  {"name":"(同人誌) >>"},
  424. //                  {"name":"(同人CG集) >>"},
  425. //                  {"name":"(18禁アニメ) >>"},
  426.                     {"name":"-"},
  427.                     {"name":"RENAME_TO_PACKAGIZER_NAME","iconKey":"reset"},
  428.                     {"name":"RENAME_TO_ORIGIN_NAME","iconKey":"reset"},
  429.                 ]
  430.             },
  431.             {
  432.                 "name":"SUBMENU_MOVE_FILE_BROWSE",
  433.                 "iconKey":"folder_add",
  434.                 "items":[
  435.                     {
  436.                         "name":"MOVETO_NEWPACKAGE_WITH_FILENAME",
  437.                         "iconKey":"package_new",
  438.         //              "iconKey":"folder_add",
  439.         //              "shortcut"  :"pressed F9"
  440.                     },
  441.                     {
  442.                         "actionData" : {"setup":{"LOCATION":"AFTER_SELECTION"},"clazzName":"org.jdownloader.gui.views.linkgrabber.contextmenu.MergeToPackageAction"},
  443. //                      "name"       : "MERGETOPACKAGEACTION",
  444.                     },
  445.                     {"name":"MOVE_SAMENAME_PACKAGES_TO_PACKAGE","actionData":{"clazzName":"org.jdownloader.gui.views.downloads.action.MergeSameNamedPackagesAction"}},
  446.                     {"name":"-"},
  447.                     {
  448.                         "actionData" : {"clazzName":"org.jdownloader.gui.toolbar.action.MoveToTopAction"},
  449. //                      "name"       : "最上部に移動",
  450. //                      "shortcut"   : "alt pressed HOME",
  451.                     },
  452.                     {
  453.                         "actionData" : {"clazzName":"org.jdownloader.gui.toolbar.action.MoveUpAction"},
  454. //                      "name"       : "上に移動",
  455. //                      "shortcut"   : "alt pressed UP",
  456.                     },
  457.                     {
  458.                         "actionData" : {"clazzName":"org.jdownloader.gui.toolbar.action.MoveDownAction"},
  459. //                      "name"       : "下に移動",
  460. //                      "shortcut"   : "alt pressed DOWN",
  461.                     },
  462.                     {
  463.                         "actionData" : {"clazzName":"org.jdownloader.gui.toolbar.action.MoveToBottomAction"},
  464. //                      "name"       : "最下部に移動",
  465. //                      "shortcut"   : "alt pressed END",
  466.                     },
  467.                     {"name":"-"},
  468.                     {
  469.                         "actionData" : {
  470.                             "setup":{
  471.                                 "CONFIRMATIONDIALOGBEHAVIOR":"DISABLED",
  472.                             },
  473.                             "clazzName":"org.jdownloader.gui.views.linkgrabber.actions.ConfirmSelectionBarActionSub"
  474.                         },
  475.                         "name"       : "MOVETODOWNLOAD",
  476.                     },
  477.                     {
  478.                         "actionData" : {
  479.                             "setup":{
  480.                                 "AUTOSTART":"ENABLED",
  481.                                 "CONFIRMATIONDIALOGBEHAVIOR":"DISABLED",
  482.                             },
  483.                             "clazzName":"org.jdownloader.gui.views.linkgrabber.actions.ConfirmSelectionBarActionSub"
  484.                         },
  485.                         "name"       : "MOVETODOWNLOADANDSTART",
  486.                     },
  487.                     {
  488.                         "actionData" : {
  489.                             "setup":{
  490.                                 "AUTOSTART":"ENABLED",
  491.                                 "CONFIRMATIONDIALOGBEHAVIOR":"DISABLED",
  492.                                 "FORCEDOWNLOADS":true,
  493.                             },
  494.                             "clazzName":"org.jdownloader.gui.views.linkgrabber.actions.ConfirmSelectionBarActionSub"
  495.                         },
  496.                         "name"       : "MOVETODOWNLOADANDFORCESTART",
  497.                     },
  498.                     {
  499.                         "actionData" : {
  500.                             "setup":{
  501.                                 "CONFIRMATIONDIALOGBEHAVIOR":"ENABLED",
  502.                             },
  503.                             "clazzName":"org.jdownloader.gui.views.linkgrabber.actions.ConfirmSelectionBarActionSub"
  504.                         },
  505.                         "name"       : "MOVETODOWNLOADAFTERCONFIRM",
  506.                     },
  507.                     {"name":"-"},
  508.                     {
  509.                         "actionData" : {"clazzName":"org.jdownloader.gui.views.linkgrabber.bottombar.AddAtTopToggleAction"},
  510.                         "name"       : "ADDATTOPTOGGLEACTION",
  511.                     },
  512.                     {"name":"-"},
  513.                     {"name":"ADD_LINKS_TO_LINKGRABBER","iconKey":"linkgrabber"},
  514.                     {"name":"-"},
  515.                     {
  516.                         "actionData" : {"setup":{"DEEPDECRYPTENABLED":false},"clazzName":"org.jdownloader.gui.views.linkgrabber.bottombar.PasteLinksAction"},
  517.                         "shortcut"   : "ctrl pressed V",
  518.                     },
  519.                     {
  520.                         "actionData" : {"setup":{"DEEPDECRYPTENABLED":true},"clazzName":"org.jdownloader.gui.views.linkgrabber.bottombar.PasteLinksAction"},
  521.                         "shortcut"   : "shift ctrl pressed V",
  522.                     },
  523.                 ]
  524.             },
  525.             {
  526.                 "name":"SUBMENU_SORT",
  527.                 "iconKey":"sort",
  528.                 "items":[
  529.                     {"name":"SORT_PACKAGES_BY_TITLE_ASCENDING",           "iconKey":"exttable/sort"},
  530.                     {"name":"SORT_PACKAGES_BY_TITLE_DESCENDING",          "iconKey":"exttable/sort"},
  531.                     {"name":"SORT_PACKAGES_BY_AUTHOR_ASCENDING",          "iconKey":"exttable/sort"},
  532.                     {"name":"SORT_PACKAGES_BY_AUTHOR_DESCENDING",         "iconKey":"exttable/sort"},
  533.                     {"name":"SORT_PACKAGES_BY_CATEGORY_TITLE_ASCENDING",  "iconKey":"exttable/sort"},
  534.                     {"name":"SORT_PACKAGES_BY_CATEGORY_TITLE_DESCENDING", "iconKey":"exttable/sort"},
  535.                     {"name":"SORT_PACKAGES_BY_CATEGORY_AUTHOR_ASCENDING", "iconKey":"exttable/sort"},
  536.                     {"name":"SORT_PACKAGES_BY_CATEGORY_AUTHOR_DESCENDING","iconKey":"exttable/sort"},
  537.                     {"name":"-"},
  538.                     {"name":"SORT_PACKAGES_BY_ADDEDDATE_ASCENDING",       "iconKey":"sort"},
  539.                     {"name":"SORT_PACKAGES_BY_ADDEDDATE_DESCENDING",      "iconKey":"sort"},
  540.                     {"name":"-"},
  541.                     {"name":"SORT_PACKAGES_BY_MYRULE_ASCENDING",         "iconKey":"sort"},
  542.                     {"name":"SORT_PACKAGES_BY_MYRULE_DESCENDING",        "iconKey":"sort"},
  543.                 ]
  544.             },
  545.            
  546.             {
  547.                 "name":"SUBMENU_ENABLE_DISABLE",
  548.                 "iconKey":"ok",
  549.                 "items":[
  550.                     {"name":"ENABLE_LINKS_MYRULE_SINGLE","iconKey":"ok"},
  551.                     {"name":"ENABLE_LINKS_MYRULE_MULTIPLE","iconKey":"ok"},
  552.                     {"name":"-"},
  553.                     {"name":"INSERTMENU.ENABLE_LINKS_BY_HOST_ONLY","iconKey":"ok"},
  554.                     {"name":"-"},
  555.                     {"name":"INSERTMENU.DISABLE_LINKS_BY_HOST","iconKey":"error"},
  556.                 ]
  557.             },
  558.             {
  559.                 "name":"SUBMENU_SEARCH",
  560.                 "iconKey":"search",
  561.                 "items":[
  562.                     {
  563.                         "actionData" : {"clazzName":"org.jdownloader.gui.views.components.packagetable.actions.SearchToolbarAction"},
  564.                         "name"       : "パッケージ検索...",
  565.                     },
  566.                     {
  567.                         "name":"OPEN_EVERYTHING_BY_AUTHOR",
  568.                         "iconKey":"search",
  569. //                      "shortcut"  :"pressed F3",
  570.                     },
  571.                     {
  572.                         "name":"OPEN_EVERYTHING_BY_TITLE",
  573.                         "iconKey":"search",
  574. //                      "shortcut"  :"pressed F4",
  575.                     },
  576.                     {"name":"-"},
  577.                     {"name":"OPEN_JD_CFG_FOLDER","iconKey":"folder"},
  578.                     {"name":"-"},
  579.                     {"name":"OPEN_SOURCEURL","iconKey":"browse"},
  580.                     {"name":"-"},
  581.                     {"name":"OPEN_LINK","iconKey":"browse"},
  582.                     {"name":"INSERTMENU.OPEN_LINKS_BY_HOST","iconKey":"plugin"},
  583.                 ]
  584.             },
  585.             {
  586.                 "name":"SUBMENU_SEARCH_WEB",
  587.                 "iconKey":"browse",
  588.                 "items":[
  589.                     {"name":"INSERTMENU.SEARCH_WEB", "iconKey":"browse"},
  590.                 ],
  591.             }
  592.         ],
  593.     },
  594.    
  595.     DEBUG:true,
  596. // 【設定ここまで】
  597. };
  598.  
  599.  
  600.  
  601. UserConfig.initConfig = function()
  602. {
  603.     // mapping SEARCH_WEB
  604.     this.menuConfig.SEARCH_WEB = this.SEARCH_WEB;
  605.  
  606.     if (App.config.DEBUG)
  607.     {
  608.         // check path
  609.         keys(this.path).forEach(function(k)
  610.         {
  611.             if (!this.path[k] || !getPath(this.path[k]).isFile())
  612.                 throw new TypeError("ERROR: Config.path."+k+" is not file path.");
  613.         },this);
  614.    
  615.         // check priorityHostRule
  616.         if (isArray(this.priorityHostRule))
  617.             (this.priorityHostRule).forEach(function(k){
  618.                 if (! isRegExp(k))
  619.                     throw new TypeError("ERROR: Config.priorityHostRule, Not RegExp[] : "+k.toString());
  620.             },this);
  621.         else
  622.             throw("ERROR: Config.priorityHostRule, Not Array.");
  623.        
  624.         // check SEARCH_WEB
  625.         keys(this.SEARCH_WEB).forEach(function(k){
  626.             const e = this.SEARCH_WEB[k];
  627.             if (!keys(findBy).some(function(kk){return findBy[kk] === e[0]},this))
  628.                 throw new TypeError("ERROR: Config.SEARCH_WEB, Not findBy : "+v);
  629.             if (e[1] != null && !/^https?:\/\/.+/.test(e[1]))
  630.                 throw new TypeError("ERROR: Config.SEARCH_WEB, Not URL or null: "+e[1]);
  631.         },this);
  632.     }
  633. };
  634.  
  635. const LANGUAGE_JA      = 'ja';
  636. const LANGUAGE_EN      = 'en';
  637. const LANGUAGE_DEFAULT = LANGUAGE_EN;
  638.  
  639. const APIMethod={
  640.     queryLinks:'queryLinks',
  641.     addLinks:'addLinks',
  642.     moveLinks:'moveLinks',
  643.     movePackages:'movePackages',
  644.     movetoNewPackage:'movetoNewPackage',
  645.     moveToDownloadlist:'moveToDownloadlist',
  646.     queryLinkCrawlerJobs:'queryLinkCrawlerJobs',
  647.     startOnlineStatusCheck:'startOnlineStatusCheck',
  648.     abort:'abort',
  649. };
  650.  
  651. const API={
  652.     moveLinks:        function(linkIds,afterLinkID,destPackageID){return callAPI(this.APIName,APIMethod.moveLinks,linkIds,afterLinkID,destPackageID)},
  653.     movePackages:     function(packageIds,afterDestPackageId){return callAPI(this.APIName,APIMethod.movePackages,packageIds,afterDestPackageId)},
  654.     movetoNewPackage: function(linkIds,pkgIds,newPkgName,downloadPath){return callAPI(this.APIName,APIMethod.movetoNewPackage,linkIds,pkgIds,newPkgName,downloadPath)},
  655.     queryLinks:       function(queryParams){return callAPI(this.APIName,APIMethod.queryLinks,queryParams)},
  656.     startOnlineStatusCheck:function(linkIds,packageIds){return callAPI(this.APIName,APIMethod.startOnlineStatusCheck,linkIds,packageIds)},
  657.  
  658.     addLinks:         function(query){return callAPI(this.APIName,APIMethod.addLinks,query)},
  659.     moveToDownloadlist:function(linkIds,packageIds){return callAPI(this.APIName,APIMethod.moveToDownloadlist,linkIds,packageIds)},
  660.     queryLinkCrawlerJobs:function(query){return callAPI(this.APIName,APIMethod.queryLinkCrawlerJobs,query)},
  661.     abort:            function(jobId){return callAPI(this.APIName,APIMethod.abort,jobId)},
  662. };
  663.  
  664. /** @enum {number} btnEvents*/
  665. const btnEvents =
  666. {
  667.     NONE:                                  0,
  668.     DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON:    1,
  669.     LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON: 1<<1,
  670.     DOWNLOAD_TABLE_BOTTOM_BAR_BUTTON:      1<<2,
  671.     LINKGRABBER_BOTTOM_BAR_BUTTON:         1<<3,
  672.     TriggerName:                           1<<7,    // TestRun
  673. };
  674. btnEvents.EVENT_DLC          = btnEvents.DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON | btnEvents.DOWNLOAD_TABLE_BOTTOM_BAR_BUTTON;
  675. btnEvents.EVENT_LGC          = btnEvents.LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON | btnEvents.LINKGRABBER_BOTTOM_BAR_BUTTON;
  676. btnEvents.EVENT_CONTEXT_MENU = btnEvents.DOWNLOAD_TABLE_CONTEXT_MENU_BUTTON | btnEvents.LINKGRABBER_TABLE_CONTEXT_MENU_BUTTON;
  677. btnEvents.EVENT_BOTTOM_BAR_BUTTON = btnEvents.DOWNLOAD_TABLE_BOTTOM_BAR_BUTTON | btnEvents.LINKGRABBER_BOTTOM_BAR_BUTTON;
  678. btnEvents.EVENT_DLC_AND_LGC  = btnEvents.EVENT_DLC | btnEvents.EVENT_LGC;
  679. btnEvents.isDLC  = function(b){return 0!=((btnEvents[b]||0)&btnEvents.EVENT_DLC)};
  680. btnEvents.isLGC  = function(b){return 0!=((btnEvents[b]||0)&btnEvents.EVENT_LGC)};
  681. btnEvents.isMenu = function(b){return 0!=((btnEvents[b]||0)&btnEvents.EVENT_CONTEXT_MENU)};
  682. btnEvents.isBottomBar = function(b){return 0!=((btnEvents[b]||0)&btnEvents.EVENT_BOTTOM_BAR_BUTTON)};
  683. btnEvents.isTestRun = function(b){return 0!=((btnEvents[b]||0)&btnEvents.TriggerName)};
  684.  
  685. function simpleClone(obj)
  686. {
  687.     const newObj = (obj instanceof Array) ? [] : {};
  688.     for (var prop in obj)
  689.         newObj[prop] = (obj[prop] != null && typeof obj[prop] === 'object') ? simpleClone(obj[prop]) : obj[prop];
  690.     return newObj;
  691. }
  692. // from https://github.com/jsPolyfill
  693. Array.prototype.find = function(callback)
  694. {
  695.     if (this === null){
  696.         throw new TypeError('Array.prototype.find called on null or undefined');
  697.     }else if (typeof callback !== 'function'){
  698.         throw new TypeError('callback must be a function');
  699.     }
  700.     var list = Object(this);
  701.     var thisArg = arguments[1];
  702.     var idx = list.findIndex(callback,thisArg);
  703.     if (idx !== -1) return list[idx];
  704. };
  705.  
  706. Array.prototype.findIndex = function(callback)
  707. {
  708.     if (this === null){
  709.         throw new TypeError('Array.prototype.find called on null or undefined');
  710.     }else if (typeof callback !== 'function'){
  711.         throw new TypeError('callback must be a function');
  712.     }
  713.     var list = Object(this);
  714.     var thisArg = arguments[1];
  715.     var resultIndex = -1;
  716.     list.some(function(el,i,li){if(callback.call(thisArg, el,i,li))return(resultIndex=i,1)});
  717.     return resultIndex;
  718. };
  719.  
  720. function getGlobalThis(){return this}
  721. function zeroPadding(s,l){return((s.toString().length<l?'0'.repeat(l-s.toString().length):'')+s.toString())}
  722. function DQ(s){ return '"'+ s.trim() +'"'}
  723. function unDQ(s){return s.replace(/^[\s\xA0]*"?|"?[\s\xA0]*$/g, '')}
  724. const keys = Object.keys;
  725. function values(k){return keys(k).map(function(x){return k[x]})}
  726. function isRegExp(x){return Object.prototype.toString.call(x) === '[object RegExp]'}
  727. function isFunction(x){return typeof x === 'function'||x instanceof Function}
  728. function isObject(x){return x!=null&&typeof x==='object'}
  729. function isString(x){return typeof x === 'string'||x instanceof String}
  730. const isArray = Array.isArray;
  731. function T(s){return (s!=null)?s:''}
  732. function defNoEnumProps(o,y){(isArray(o)?o:[o]).forEach(function(x){keys(y).forEach(function(z){Object.defineProperty(x,z,{enumerable:false,value:y[z]})})})};
  733. function flatter(list)
  734. {
  735.     return list!=null ? (isArray(list)
  736.         ? list.reduce(function(res, x, i, li)
  737.             {
  738.                 if (isArray(x))
  739.                     Array.prototype.push.apply(res, flatter(x));
  740.                 else
  741.                     li.hasOwnProperty(i) ? res.push(x) : res.length++;
  742.                 return res;
  743.             },[])
  744.         : [list]) : [];
  745. }
  746. function getFileParts(f)
  747. {
  748.     const r = T(f).match(/^(.+?)(\.part\d+|\.mp3|\.zip|\.rar|\.tar|\.epub)?(\.[\da-z]{2,6})$/i);
  749.     return (r && !/^\d+$/.test(r[3]))?[T(r[1]),T(r[2]),T(r[3])]:[T(f),'',''];
  750. }
  751. function getFileSpec(f){return (getFileParts(f))[0]}
  752. function getFileSpecFull(f){const x=getFileParts(f);return x[0]+x[1];}
  753. function getExt(f){return (getFileParts(f))[2]}
  754. function getExtFull(f){const x=getFileParts(f);return x[1]+x[2];}
  755.  
  756. function getxUrl(l){return l.pluginURL||l.contentURL||l.getProperty('URL_CONTENT')||''}
  757. function getxUrl2(l){return getxUrl(l).replace(/#.*$/,'')}
  758. //function getxUrl(x){return x.getUrl()||''}
  759. function xUUID(x){return x.UUID}
  760. function xName(x){return x.getName()}
  761. function xDup(x,i,l){return l.indexOf(x)===i}
  762. function isJavaObject(obj){return obj!=null && '[object JavaObject]' === Object.prototype.toString.call(obj)}
  763. function isJdObject(obj,sign)
  764. {
  765.     return isJavaObject(obj) && (obj.toString()||'').indexOf(sign)===0
  766. }
  767. function isCrawledLink(obj){return isJdObject(obj, 'CrawledLink Instance:')}
  768. function isCrawledPackage(obj){return isJdObject(obj, 'CrawledPackage Instance:')}
  769. function isDownloadLink(obj){return isJdObject(obj, 'DownloadLink Instance:')}
  770. function isFilePackage(obj){return isJdObject(obj, 'FilePackage Instance:')}
  771. function isDownloadPackage(obj){return isFilePackage(obj)}
  772. function isLinkgrabberSelection(obj){return isJdObject(obj, 'org.jdownloader.extensions.eventscripter.sandboxobjects.LinkgrabberSelectionSandbox@')}
  773. function isDownloadSelection(obj){return isJdObject(obj, 'org.jdownloader.extensions.eventscripter.sandboxobjects.DownloadlistSelectionSandbox@')}
  774. function isCrawlerJob(obj){return isJdObject(obj, 'org.jdownloader.extensions.eventscripter.sandboxobjects.CrawlerJobSandbox@')}
  775.  
  776.  
  777. function jdPath(path)
  778. {
  779.     const sep = getPathSeparator();
  780.     if (sep == '\\')
  781.         path = path.replace('/', '\\');
  782.     else if (sep == '/')
  783.         path = path.replace('\\', '/');
  784.     return JD_HOME+sep+path;
  785. }
  786.  
  787. /*
  788. function jdFormatString(str, replacers)
  789. {
  790.     return str.replace(/%s(\d)/g,function(m,n){return --n<replacers.length?replacers[n]:m})
  791.         .replace(/\\([\\"nrt])/g, function(m,m1){return {'\\':'\\','"':'"','n':'\n','r':'\r','t':'\t'}[m1]});   //"
  792. }
  793.  
  794. */
  795.  
  796. function jdFormatString(str, replacers)
  797. {
  798.     var rep = flatter(replacers);
  799.     const max = rep.length;
  800.     const sp = {'\\':'\\','"':'"','n':'\n','r':'\r','t':'\t'};
  801.  
  802.     var tmp = str;
  803.     var len = tmp.length;
  804.     var output = [];
  805.     // replace special char
  806.     for (var cur=0,pos=0; cur<len; cur=pos)
  807.     {
  808.         pos = tmp.indexOf("\\", cur);
  809.         if (pos == -1) pos = len;
  810.         output.push(tmp.substr(cur,pos-cur));
  811.         if (len < (++pos)) break;
  812.         output.push(sp[tmp[pos]]||tmp[pos]);
  813.         pos++;
  814.     }
  815.  
  816.     tmp = output.join('');
  817.     len = tmp.length;
  818.     output=[];
  819.     // replace format words with replacers
  820.     var idx=0;
  821.     for (var cur=0,pos=0; cur<len; cur=pos)
  822.     {
  823.         pos = tmp.indexOf("%s", cur);
  824.         if (pos == -1) pos = len;
  825.         output.push(tmp.substr(cur,pos-cur));
  826.         if (len < (pos+=2)) break;
  827.         idx = parseInt(tmp[pos++])-1;
  828.         if (0<=idx && idx<max) output.push(rep[idx]||'');
  829.     }
  830.     tmp = output.join('');
  831.    
  832.     return tmp;
  833. }
  834.  
  835. /**
  836.  * 文字列の正規化
  837.  *
  838.  * @param   {string} s 変換前の文字列
  839.  * @returns {string} 変換後の文字列
  840.  *
  841.  * ES5環境にはString.prototype.normalize()が無い
  842.  */
  843. function normalizeTitle(s)
  844. {
  845.     if (s===undefined||s===null||s==="") return s;
  846.    
  847.     /** @type {object.<string:string>} 半角記号の変換テーブル */
  848.     const NT1 = {
  849. "\!":"!","\"":"”","\$":"$","\%":"%","\&":"&","'":"’","~":" ̄","|":"|",
  850. "*":"*",":":":","<":"<",">":">","?":"?","/":"/","\\":"¥","`":"‘",",":","
  851. //特殊濁点半濁点→日本語濁点半濁点(分離)
  852. //"\u3099":"\u309B","\u309A":"\u309C"
  853.     };
  854.     /** @type {object.<string:string>}} 文字列の変換テーブル */
  855.     const NT2 = {
  856. //記号
  857. " ":" ","〜":"~","&times;":"+","&amp;":"&","♡":"","♥":"","―":"-","─":"-","━":"-",
  858.  
  859. //半角カナ→全角カナ
  860. 'ガ': 'ガ', 'ギ': 'ギ', 'グ': 'グ', 'ゲ': 'ゲ', 'ゴ': 'ゴ',
  861. 'ザ': 'ザ', 'ジ': 'ジ', 'ズ': 'ズ', 'ゼ': 'ゼ', 'ゾ': 'ゾ',
  862. 'ダ': 'ダ', 'ヂ': 'ヂ', 'ヅ': 'ヅ', 'デ': 'デ', 'ド': 'ド',
  863. 'バ': 'バ', 'ビ': 'ビ', 'ブ': 'ブ', 'ベ': 'ベ', 'ボ': 'ボ',
  864. 'パ': 'パ', 'ピ': 'ピ', 'プ': 'プ', 'ペ': 'ペ', 'ポ': 'ポ',
  865. 'ヴ': 'ヴ', 'ヷ': 'ヷ', 'ヺ': 'ヺ',
  866. 'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
  867. 'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
  868. 'サ': 'サ', 'シ': 'シ', 'ス': 'ス', 'セ': 'セ', 'ソ': 'ソ',
  869. 'タ': 'タ', 'チ': 'チ', 'ツ': 'ツ', 'テ': 'テ', 'ト': 'ト',
  870. 'ナ': 'ナ', 'ニ': 'ニ', 'ヌ': 'ヌ', 'ネ': 'ネ', 'ノ': 'ノ',
  871. 'ハ': 'ハ', 'ヒ': 'ヒ', 'フ': 'フ', 'ヘ': 'ヘ', 'ホ': 'ホ',
  872. 'マ': 'マ', 'ミ': 'ミ', 'ム': 'ム', 'メ': 'メ', 'モ': 'モ',
  873. 'ヤ': 'ヤ', 'ユ': 'ユ', 'ヨ': 'ヨ',
  874. 'ラ': 'ラ', 'リ': 'リ', 'ル': 'ル', 'レ': 'レ', 'ロ': 'ロ',
  875. 'ワ': 'ワ', 'ヲ': 'ヲ', 'ン': 'ン',
  876. 'ァ': 'ァ', 'ィ': 'ィ', 'ゥ': 'ゥ', 'ェ': 'ェ', 'ォ': 'ォ',
  877. 'ッ': 'ッ', 'ャ': 'ャ', 'ュ': 'ュ', 'ョ': 'ョ',
  878. '。': '。', '、': '、', 'ー': 'ー', '「': '「', '」': '」', '・': '・',
  879. //よくある簡体字、繁体字→日本語常用漢字
  880. '卷':'巻','學':'学','黑':'黒','關':'関','繪':'絵','會':'会','亞':'亜','髙':'高','户':'戸',
  881. '說':'説','说':'説'     //繁体字、簡体字の"説"
  882.     };
  883.    
  884.     /** @type {object.<string:string>} 濁点半濁点結合 */
  885.     const NT3={
  886. 'ウ゛':'ヴ','ワ゛':'ヷ','ヲ゛':'ヺ',
  887. 'カ゛':'ガ','キ゛':'ギ','ク゛':'グ','ケ゛':'ゲ','コ゛':'ゴ',
  888. 'サ゛':'ザ','シ゛':'ジ','ス゛':'ズ','セ゛':'ゼ','ソ゛':'ゾ',
  889. 'タ゛':'ダ','チ゛':'ヂ','ツ゛':'ヅ','テ゛':'デ','ト゛':'ド',
  890. 'ハ゛':'バ','ヒ゛':'ビ','フ゛':'ブ','ヘ゛':'ベ','ホ゛':'ボ',
  891. 'ハ゜':'パ','ヒ゜':'ピ','フ゜':'プ','ヘ゜':'ペ','ホ゜':'ポ',
  892. 'か゛':'が','き゛':'ぎ','く゛':'ぐ','け゛':'げ','こ゛':'ご',
  893. 'さ゛':'ざ','し゛':'じ','す゛':'ず','せ゛':'ぜ','そ゛':'ぞ',
  894. 'た゛':'だ','ち゛':'ぢ','つ゛':'づ','て゛':'で','と゛':'ど',
  895. 'は゛':'ば','ひ゛':'び','ふ゛':'ぶ','へ゛':'べ','ほ゛':'ぼ',
  896. 'は゜':'ぱ','ひ゜':'ぴ','ふ゜':'ぷ','へ゜':'ぺ','ほ゜':'ぽ',
  897.     };
  898. //  var pat1=new RegExp('(?:['+Object.keys(NT1).map(c=>'\\'+c).join('')+']', 'g');
  899. //  alert('(?:['+Object.keys(NT1).join('')+']');
  900.  
  901.     const pat1=new RegExp('['+keys(NT1).join('')+']', 'g');
  902.     const pat2=new RegExp('(?:' + keys(NT2).join('|') + ')', 'g');
  903. //  const pat3=new RegExp('(['+Object.keys(NT3).map(function(s){return s[0]}).join('')+'])([\u3099\u309B\u309A\u309C])', 'g');
  904.     const pat3=/([ウワヲカキクケコサシスセソタチツテトハヒフヘホうかきくけこさしすせそたちつてとはひふへほ])([\u3099\u309B\u309A\u309C])/g;
  905.     return( s
  906.             .replace(/[A-Za-z0-9]/g, function(s){return String.fromCharCode(s.charCodeAt(0)-0xFEE0)})
  907.             .replace(pat1,function(m){return(m in NT1?NT1[m]:m)})
  908.             .replace(pat2,function(m){return(m in NT2?NT2[m]:m)})
  909.             .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)})
  910.             .replace(/([\]\)])([^\s\.])/g, '$1 $2')
  911.             .replace(/([^ ])([\[\(])/g, '$1 $2')
  912.             .replace(/  +/g," ")
  913.         );
  914. }
  915.  
  916. function getDefaultDownloadFolder()
  917. {
  918.     return callAPI("config", "get", "org.jdownloader.settings.GeneralSettings", null, "DefaultDownloadFolder");
  919. }
  920.  
  921. /**
  922.  * パッケージ上で右クリックしたのなら選択中のパッケージリストを
  923.  *     リンク上で右クリックしたのなら選択中のリンクリストを返す
  924.  *
  925.  * @function
  926.  * @param   {object} sel lgSelection or dlSelection
  927.  * @returns {object[]}   packages or links or []
  928.  */
  929. function getItemsByContextType(sel)
  930. {
  931.     return (sel.isPackageContext() ? sel.packages : (sel.isLinkContext() ? sel.links : []));
  932. }
  933.  
  934. /**
  935.  * 右クリックされたパッケージかリンクを返す
  936.  *
  937.  * @param   {object} sel  lgSelection or dlSelection
  938.  * @returns {object|null} package or link or null
  939.  */
  940. function getContextItemByContextType(sel)
  941. {
  942.     return (sel.isPackageContext() ? sel.contextPackage : (sel.isLinkContext() ? sel.contextLink : null));
  943. }
  944.  
  945.  
  946.  
  947. // "Script" が使えない(´・ω・`)
  948. const App = {
  949.     config:UserConfig,
  950.     translation:null,
  951.     resource:null,
  952.     dispatcher:null,
  953. };
  954.  
  955. //App.config = UserConfig;
  956.  
  957. App.jdFormat = function(fmt, rep)
  958. {
  959.     return jdFormatString(this.getResource(fmt), rep!=null?(isArray(rep)?rep:[rep]).map(function(r){return this.getResource(r)},this):undefined);
  960. };
  961. /**
  962.  * JDの言語ファイル関連
  963.  *
  964.  *   JD_UISetting_Language = translation.language;
  965.  *   translation.isAutoCreatedPackageName(package_name)
  966.  *   translation.isDefaultPackageName(package_name)
  967.  *
  968.  * 今のところ、取り扱いは以下の3ファイル
  969.  *   cfg/language.json
  970.  *   translations/org/jdownloader/translate/JdownloaderTranslation.*.lng
  971.  *   translations/org/jdownloader/gui/translate/GuiTranslation.*.lng
  972.  */
  973. App.translation =
  974. {
  975.     SEPARATOR                       : 'SeparatorData_SeparatorData',                    // セパレータ
  976.     VARIOUS_PACKAGE                 : 'LinkCollector_addCrawledLink_variouspackage',    // 様々なファイル
  977.     DEFAULT_PACKAGE                 : 'controller_packages_defaultname',                // 任意
  978.     OFFLINE_PACKAGE                 : 'LinkCollector_addCrawledLink_offlinepackage',    // オフラインファイル
  979.     PERMANENTLY_OFFLINE_PACKAGE     : 'Permanently_Offline_Package',                    // 永続オフライン
  980.     HEXLOAD_PACKAGE                 : 'Folder',                                         // hexload.com plugin
  981.     PATH_CFG_LANGUAGE_JSON          : 'cfg\\language.json',
  982.     PATH_TRANSLATION_GUITRANSLATE   : 'translations\\org\\jdownloader\\gui\\translate',
  983.     PATH_TRANSLATION_TRANSLATE      : 'translations\\org\\jdownloader\\translate',
  984.     PREFIX_TRANSLATION_GUITRANSLATE : 'GuiTranslation',
  985.     PREFIX_TRANSLATION_TRANSLATE    : 'JdownloaderTranslation',
  986.     EXTENTION_LNG_FILE              : '.lng',
  987.    
  988.     /**
  989.      * JDのlanguageを取得(JD本体側の「UI設定の言語」)
  990.      *
  991.      * @return {string='en'}
  992.      *
  993.      * 返すlanguage文字列は[-a-zA-Z\d_]+の範囲内の文字列("en"|"ja"|etc...)
  994.      * デフォルトは en
  995.      *
  996.      * ファイル'cfg\\language.json'から取得
  997.      * この文字列はJDをリスタートするまで不変
  998.      * 値はキャッシュする
  999.      */
  1000.     _language : null,
  1001.     get language() {
  1002.    
  1003. //  return LANGUAGE_EN;             // for DEBUG english lang
  1004.  
  1005.             if (this._language) return this._language;
  1006.             const buf = unDQ(readFile(jdPath(this.PATH_CFG_LANGUAGE_JSON)));
  1007.             return this._language = (buf != '' && /^[-a-zA-Z\d_]+$/.test(buf)) ? buf : 'en';
  1008.     },
  1009.    
  1010.     /**
  1011.      * JDの言語ファイル(*.lng)を読み込み変換テーブルを取得
  1012.      * ファイル毎の最小限のキャッシュを生成(ファイル全体のデータは破棄し、指定したキーのみキャッシュ)
  1013.      *
  1014.      * @param {string} path_lng 取得する言語ファイルへのパス
  1015.      * @param {string[]|string} filter_keys 取得する文字列を指定するキーorキー配列、引数無しで全取得
  1016.      * @return {TranslateResourceTable|null} 指定した言語ファイルから取得した文字列変換テーブル
  1017.      * -@return {object.<string,string>|null}
  1018.      *
  1019.      * ※中身が/(key:[^=\s]+)=(value:[^\n]*)\n/のファイルを読む関数
  1020.      *
  1021.      */
  1022.     getTranlation : function(path_lng, filter_keys)
  1023.     {
  1024.         if (! this.getTranlation.cache) this.getTranlation.cache = {};
  1025.        
  1026.         const ret = {};
  1027.         if (filter_keys)
  1028.         {
  1029.             if (!isArray(filter_keys))
  1030.             {
  1031.                 if (typeof filter_keys !== 'string')
  1032.                     return null;
  1033.                
  1034.                 filter_keys = [filter_keys];
  1035.             }
  1036.            
  1037.             if (this.getTranlation.cache[path_lng])
  1038.             {
  1039.                 filter_keys = filter_keys.filter(function(k)
  1040.                 {
  1041.                     if (! (this.getTranlation.cache[path_lng][k]) )
  1042.                         return true;
  1043.                    
  1044.                     ret[k] = this.getTranlation.cache[path_lng][k];
  1045.                     return false;
  1046.                 }, this);
  1047.                 if (keys(filter_keys).length == 0)
  1048.                     return ret;
  1049.             }
  1050.         }
  1051.         const buf = readFile(path_lng);
  1052.         if (! buf) return null;
  1053.         if (! this.getTranlation.cache[path_lng]) this.getTranlation.cache[path_lng] = {};
  1054.        
  1055.         const _cache = this.getTranlation.cache[path_lng];
  1056.         if (! filter_keys)
  1057.             buf.replace(/([^=\r\n]+)=([^\r\n]*)/g,function(line,k,v){_cache[k]=ret[k]=v});
  1058.         else if (isArray(filter_keys))
  1059.             buf.replace(/([^=\r\n]+)=([^\r\n]*)/g,function(line,k,v){if(0<filter_keys.filter(function(a){return a==k}).length)_cache[k]=ret[k]=v});
  1060.         else if (typeof filter_keys === 'string')
  1061.             buf.replace(/([^=\r\n]+)=([^\r\n]*)/g,function(line,k,v){if(filter_keys==k)_cache=ret[k]=v});
  1062.         return ret;
  1063.     },
  1064.    
  1065.     _getJdownloaderTranslationXLngPath : function(lng){
  1066.         return jdPath(this.PATH_TRANSLATION_TRANSLATE+'\\'+this.PREFIX_TRANSLATION_TRANSLATE+'.'+(lng||this.language)+this.EXTENTION_LNG_FILE)
  1067.     },
  1068.     _getGuiTranslationXLngPath : function(lng){
  1069.         return jdPath(this.PATH_TRANSLATION_GUITRANSLATE+'\\'+this.PREFIX_TRANSLATION_GUITRANSLATE+'.'+(lng||this.language)+this.EXTENTION_LNG_FILE)
  1070.     },
  1071.    
  1072.     /**
  1073.      * JDの言語ファイル JdownloaderTranslation.{language}.lngを読み込んで変換テーブルを取得
  1074.      * ファイル毎の最小限のキャッシュを生成(ファイル全体のデータは破棄し、指定したキーのみキャッシュ)
  1075.      *
  1076.      * @param {string[]|string} filter_keys 取得したいキーorキー配列、引数無しで全取得
  1077.      * @param {string=this.language} lang 言語の指定(デフォルトは「JDのUI設定の言語」)
  1078.      * @return {object.<string,string>|null} 指定した言語ファイルから取得した文字列変換テーブル
  1079.      */
  1080.     getJdownloaderTranslation : function(filter_keys, lang){
  1081.         return this.getTranlation(this._getJdownloaderTranslationXLngPath(lang||this.language), filter_keys)
  1082.     },
  1083.     /**
  1084.      * JDの言語ファイル GuiTranslation.{language}.lngを読み込んで変換テーブルを取得
  1085.      * ファイル毎の最小限のキャッシュを生成(ファイル全体のデータは破棄し、指定したキーのみキャッシュ)
  1086.      *
  1087.      * @param {string[]|string} filter_keys 取得したいキーorキー配列、引数無しで全取得
  1088.      * @param {string=this.language} lang 言語の指定(デフォルトは「JDのUI設定の言語」)
  1089.      * @return {object.<string,string>|null} 指定した言語ファイルから取得した文字列変換テーブル
  1090.      */
  1091.     getGuiTranslation : function(filter_keys, lang){
  1092.         return this.getTranlation(this._getGuiTranslationXLngPath(lang||this.language), filter_keys)
  1093.     },
  1094.    
  1095.     /**
  1096.      * 自動生成されたパッケージ名かどうか判定(英語+現在の言語、2言語分チェック)
  1097.      *
  1098.      * @param {string} n パッケージ名
  1099.      * @return {boolean}
  1100.      *
  1101.      * JDownloaderの言語パッケージから翻訳データを取得しないと判別できない
  1102.      * Translationインターフェースの使い方が分からんから
  1103.      * .lngファイルから直読み
  1104.      *
  1105.      **/
  1106.     isAutoCreatedPackageName : function(n)
  1107.     {
  1108.         const langs = [this.language];
  1109.         if (langs[0] != LANGUAGE_DEFAULT) langs.push(LANGUAGE_DEFAULT);
  1110.         return langs.some(function(l)
  1111.         {
  1112.             const tmp1 = this.getJdownloaderTranslation([this.VARIOUS_PACKAGE, this.DEFAULT_PACKAGE, this.OFFLINE_PACKAGE], l);
  1113.             const tmp2 = this.getGuiTranslation([this.PERMANENTLY_OFFLINE_PACKAGE], l);
  1114.            
  1115.             return n===tmp1[this.VARIOUS_PACKAGE]
  1116.                 || n===tmp1[this.DEFAULT_PACKAGE]
  1117.                 || n===tmp1[this.OFFLINE_PACKAGE]
  1118.                 || n===tmp2[this.PERMANENTLY_OFFLINE_PACKAGE];
  1119.         },this);
  1120.     },
  1121.    
  1122.     getSeparatorName : function(lang)
  1123.     {
  1124.         var tmp = this.getGuiTranslation([this.SEPARATOR], lang);
  1125.         if (!tmp[this.SEPARATOR])
  1126.         {
  1127.             // fall back
  1128.             tmp = this.getGuiTranslation([this.SEPARATOR], LANGUAGE_DEFAULT);
  1129.             if (!tmp[this.SEPARATOR])
  1130.             {
  1131.                 return null;
  1132.             }
  1133.         }
  1134.         return tmp[this.SEPARATOR];
  1135.     },
  1136.  
  1137.     /**
  1138.      * デフォルトのパッケージ名かどうか判定(英語+現在の言語、2言語分チェック)
  1139.      *
  1140.      * @param {string} n パッケージ名
  1141.      * @return {boolean}
  1142.      **/
  1143.     isDefaultPackageName : function(n)
  1144.     {
  1145.         const langs = [this.language];
  1146.         if (langs[0] != LANGUAGE_DEFAULT) langs.push(LANGUAGE_DEFAULT);
  1147.         return langs.some(function(l)
  1148.         {
  1149.             const tmp = this.getJdownloaderTranslation([this.VARIOUS_PACKAGE, this.DEFAULT_PACKAGE], l);
  1150.             return n===tmp[this.VARIOUS_PACKAGE]
  1151.                 || n===tmp[this.DEFAULT_PACKAGE];
  1152.         },this);
  1153.     },
  1154. };
  1155.  
  1156. App.isAutoCreatedPackageName = function(n){return this.translation.isAutoCreatedPackageName(n)};
  1157. App.isDefaultPackageName     = function(n){return this.translation.isDefaultPackageName(n)};
  1158. App.getResource = function(id, lang){return this.resource.getResource(id, lang||this.script_language)};
  1159. App.isDLC              = function(){return btnEvents.isDLC(this.menu)};
  1160. App.isLGC              = function(){return btnEvents.isLGC(this.menu)};
  1161. App.getSelection       = function(){return this.CTX.selection};
  1162. App.checkEventSource   = function(allowedEvents){return btnEvents[this.menu]&&0!=(btnEvents[this.menu]&allowedEvents)};
  1163. App.initResourceTables = function()
  1164. {
  1165.     var r = {
  1166.         // 日本語
  1167.         'ja':
  1168.         {
  1169.             OPEN_EVERYTHING_BY_TITLE                    : 'Everythingで検索(タイトル)',
  1170.             OPEN_EVERYTHING_BY_AUTHOR                   : 'Everythingで検索(作者名)',
  1171.             OPEN_LINK                                   : 'ブラウザで開く',
  1172.             OPEN_JD_CFG_FOLDER                          : 'JDバックアップフォルダを開く',
  1173.             OPEN_SOURCEURL                              : '登録元ページを開く',
  1174.             // リネーム系
  1175.             RENAME_LINKS_BY_PACKAGENAME                 : 'パッケージ名で名前を揃える',
  1176.             RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME : 'このリンクの名前でパッケージ内を全て揃える',
  1177.             RENAME_LINK_BY_LINKNAME                     : '名前を揃える',     // 複数のリンクを選択してから右クリックし、右クリック直下のファイル名 (○○) [△△] □□.zip→(○○) [△△] □□を揃える
  1178.             RENAME_LINK_BY_LINK_AUTHOR                  : '名前を揃える(作者名)',  // (○○) [△△] □□.zip→[△△]部を揃える
  1179.             RENAME_LINK_BY_LINK_TITLE                   : '名前を揃える(タイトル)',   // (○○) [△△] □□ ~××~第01巻.zip→□□部を揃える
  1180.             RENAME_LINK_BY_LINK_CATEGORY_AUTHOR         : '名前を揃える(カテゴリ+作者名)',
  1181.             RENAME_LINK_BY_LINK_LONGTITLE               : '名前を揃える(カテゴリ+作者名+タイトル)',
  1182.             RENAME_STARTING_BRACKETS_MOVETO_END         : '先頭括弧を後方送り',
  1183.             RENAME_REMOVE_TRAILING_BRACKETS             : '末尾の括弧削除',
  1184.             RENAME_NUMBERING_FORMAT                     : '第○○巻化',
  1185.             RENAME_AUTHOR_SWAP                          : '[]内のスワップ',   // [○○×△△]→[△△×○○]
  1186.             RENAME_AUTHOR_CHOP                          : '[]内の末尾切り捨て', // [○○×△△×□□]→[○○×△△]
  1187.             RENAME_AUTHOR_X_TO_BATSU                    : '[]内のxを×に変換',    // [○○x△△]→[○○×△△]
  1188.             RENAME_TO_PACKAGIZER_NAME                   : '名前を元に戻す(パッケージャ適用後)...',
  1189.             RENAME_TO_ORIGIN_NAME                       : '名前を元に戻す...',
  1190.             RENAME_ALIGN_DIGITS_LENGTH                  : '名前の数字の桁揃え',
  1191.             RENAME_FULLWIDTH_TO_HALFWIDTH               : '名前の全角英数字を半角に変換',
  1192.             RENAME_NORMALIZE                            : '名前の正規化',
  1193.             RENAME_URLDECODE                            : '名前のURLデコード',
  1194.             // 移動系
  1195.             MOVETO_NEWPACKAGE_WITH_FILENAME             : 'この名前で新しいパッケージに移動',
  1196.             MOVE_SAMENAME_PACKAGES_TO_PACKAGE           : '同じ名前のパッケージをまとめる',
  1197.             ADD_LINKS_TO_LINKGRABBER                    : 'リンクグラバーへ再登録',
  1198.  
  1199.             SORT_PACKAGES_BY_TITLE_ASCENDING            : 'タイトルで並べ替え(昇順)',
  1200.             SORT_PACKAGES_BY_TITLE_DESCENDING           : 'タイトルで並べ替え(降順)',
  1201.             SORT_PACKAGES_BY_AUTHOR_ASCENDING           : '作者名で並べ替え(昇順)',
  1202.             SORT_PACKAGES_BY_AUTHOR_DESCENDING          : '作者名で並べ替え(降順)',
  1203.             SORT_PACKAGES_BY_CATEGORY_TITLE_ASCENDING   : 'カテゴリ別タイトルで並べ替え(昇順)',
  1204.             SORT_PACKAGES_BY_CATEGORY_TITLE_DESCENDING  : 'カテゴリ別タイトルで並べ替え(降順)',
  1205.             SORT_PACKAGES_BY_CATEGORY_AUTHOR_ASCENDING  : 'カテゴリ別作者名で並べ替え(昇順)',
  1206.             SORT_PACKAGES_BY_CATEGORY_AUTHOR_DESCENDING : 'カテゴリ別作者名で並べ替え(降順)',
  1207.             SORT_PACKAGES_BY_ADDEDDATE_ASCENDING        : '追加日時で並べ替え(昇順)',
  1208.             SORT_PACKAGES_BY_ADDEDDATE_DESCENDING       : '追加日時で並べ替え(降順)',
  1209.             SORT_PACKAGES_BY_MYRULE_ASCENDING           : '優先ホストで並べ替え(昇順)',
  1210.             SORT_PACKAGES_BY_MYRULE_DESCENDING          : '優先ホストで並べ替え(降順)',
  1211.            
  1212.             ENABLE_LINKS_MYRULE_SINGLE                  : '優先ホスト(単一)のみ有効',
  1213.             ENABLE_LINKS_MYRULE_MULTIPLE                : '優先ホスト(複数)のみ有効',
  1214.            
  1215.             RENAME_ADD_TO_BEGIN_REGEXP                  : /^(.+) *>>$/,
  1216.             RENAME_ADD_TO_END_REGEXP                    : /^<< *(.+)$/,
  1217.             ENABLE_LINKS_BY_HOST_ONLY_REGEXP            : /^([\da-z][-\da-zA-Z\.]+) *のみ有効$/,
  1218.             DISABLE_LINKS_BY_HOST_REGEXP                : /^([\da-z][-\da-zA-Z\.]+) *を無効$/,
  1219.             OPEN_LINKS_BY_HOST_REGEXP                   : /^([-\da-zA-Z\.]+\.[\da-z]{2,4}) *のみ開く/,
  1220.            
  1221.             RENAME_ADD_TO_BEGIN                         : '%s1 >>',
  1222.             RENAME_ADD_TO_END                           : '<< %s1',
  1223.             ENABLE_LINKS_BY_HOST_ONLY                   : '%s1 のみ有効',
  1224.             DISABLE_LINKS_BY_HOST                       : '%s1 を無効',
  1225.             OPEN_LINKS_BY_HOST                          : '%s1 のみ開く',
  1226.             SEARCH_WEB                                  : '%s1',
  1227.  
  1228.             SUBMENU_RENAME_ALIGN                        : 'リネーム - 名前を揃える',
  1229.             SUBMENU_RENAME_ADD_REPLACE                  : 'リネーム - 追加/置換',
  1230.             SUBMENU_ENABLE_DISABLE                      : '有効/無効',
  1231.             SUBMENU_SORT                                : '並べ替え',
  1232.             SUBMENU_MOVE_FILE_BROWSE                    : '移動/登録',
  1233.             SUBMENU_SEARCH_WEB                          : 'WEB検索',
  1234.             SUBMENU_SEARCH                              : '開く/検索/その他',
  1235.            
  1236.             SEARCH_GOOGLE_AUTHOR        : 'google.com (作者)',
  1237.             SEARCH_GOOGLE_TITLE         : 'google.com (タイトル)',
  1238.             MERGETOPACKAGEACTION        : '新しいパッケージへ移動...',
  1239.             MOVETODOWNLOAD              : 'ダウンロードに追加',
  1240.             MOVETODOWNLOADANDSTART      : 'ダウンロードに追加して開始',
  1241.             MOVETODOWNLOADANDFORCESTART : 'ダウンロードに追加して強制的に開始',
  1242.             MOVETODOWNLOADAFTERCONFIRM  : 'ダウンロードに追加...',
  1243.             ADDATTOPTOGGLEACTION        : 'ダウンロード追加時の位置を最上部に設定',
  1244.            
  1245.             DownloadTableContext    : "ダウンロードリスト - 右クリックメニュー",
  1246.             LinkgrabberContext      : "リンクグラバーリスト - 右クリックメニュー",
  1247. //          DownloadTabBottomBar    : "ダウンロードリスト下部バー",
  1248. //          LinkgrabberTabBottomBar : "リンクグラバー下部バー",
  1249.  
  1250.             SEPERATOR:
  1251.                 'セパレータ',
  1252.             INSTALLMENU_MESSAGE:
  1253.                 '────────────────────────────\n'+
  1254.                 '      右クリックメニューのインストール      \n'+
  1255.                 '────────────────────────────\n\n'+
  1256.                 '%s1\n'+
  1257.                 '────────────────────────────\n'+
  1258.                 '%s2',
  1259.             INSTALLMENU_INTRO:
  1260.                 '右クリックメニューに項目を追加します\n\n'+
  1261.                 ' ・リンクグラバー - 右クリックメニュー\n'+
  1262.                 ' ・ダウンロードリスト - 右クリックメニュー\n\n'+
  1263.                 '【注意】\n'+
  1264.                 '・追加されたメニューの項目は、\n'+
  1265.                 ' 後から『 メニュー管理画面 』で編集が可能\n'+
  1266.                 '・インストール前に各メニューのバックアップファイルを作成\n'+
  1267.                 '・バックアップしたファイルは『 メニュー管理画面 』から\n'+
  1268.                 ' インポートすることで、以前の状態に復元可能\n'+
  1269.                 '\n'+
  1270.                 '(『 次へ... 』ボタンを押すと開始され、最後まで進むと\n'+
  1271.                 '『 インストールを実行 』します)',
  1272.             INSTALLMENU_LANG:
  1273.                 '\nインストールするメニューの言語を選択してください\n\n',
  1274.             INSTALLMENU_MENU:
  1275.                 '『 %s1 』に\n\nメニューを追加しますか?\n\n\n'+
  1276.                 '※ 現在使用されているメニューのバックアップは\n'+
  1277.                 'JDの /cfg/ フォルダに保存されます',
  1278.             INSTALLMENU_EXECUTE:
  1279.                 '以下のメニューの『バックアップファイルを作成』した後、\n'+
  1280.                 '『 インストールを実行 』 します\n\n'+
  1281.                 'よろしいですか?\n\n\n'+
  1282.                 '%s1\n'+
  1283.                 '※ 後から『 メニュー管理画面 』で保存されたバックアップファイルを\n'+
  1284.                 '  インポートすることにより、メニューを以前の状態に復元できます。',
  1285.             INSTALLMENU_EBACKUP:
  1286.                 'ERROR: バックアップファイルの作成に失敗しました\n\n'+
  1287.                 'バックアップ無しでインストールを継続しますか?\n\n\n'+
  1288.                 '%s1',
  1289.             INSTALLMENU_RESTART:
  1290.                 'インストールが完了しました\n\n'+
  1291.                 '設定の反映には『 JDownloaderの再起動 』が必要です\n'+
  1292.                 '今すぐJDownloaderを再起動しますか?\n\n\n'+
  1293.                 '%s1',
  1294.             INSTALLMENU_DONE:
  1295.                 'インストールが完了しました\n'+
  1296.                 '終了します\n\n\n'+
  1297.                 '%s1',
  1298.             INSTALLMENU_CANCEL:
  1299.                 'インストールがキャンセルされました\n'+
  1300.                 '終了します\n',
  1301.             INSTALLMENU_BACKUP_DONE:
  1302.                 'バックアップ済み',
  1303.             INSTALLMENU_BACKUP_NO:
  1304.                 'バックアップ前',
  1305.             INSTALLMENU_BACKUP_ERROR:
  1306.                 'バックアップ失敗',
  1307.             INSTALLMENU_INSTALL_DONE:
  1308.                 '完了',
  1309.             INSTALLMENU_INSTALL_NO:
  1310.                 '未インストール',
  1311.             INSTALLMENU_INSTALL_ERROR:
  1312.                 '失敗',
  1313.             INSTALLMENU_RESULT:
  1314.                 '【  メニュー種別】『%s1 (%s2)』\n'+
  1315.                 '【インストール進捗】%s3\n'+
  1316.                 '【バックアップ進捗】%s4\n'+
  1317.                 '【 バックアップ先】"%s5"',
  1318.             INSTALLMENU_INDICATOR_1:
  1319.                 '→',
  1320.             INSTALLMENU_INDICATOR_2:
  1321.                 ' ',
  1322.             INSTALLMENU_INDICATOR_MSG:
  1323.                 '・インストールモードの開始確認\n'+
  1324.                 '・インストールする右クリックメニューの言語選択\n'+
  1325.                 '・インストールする右クリックメニューの選択\n'+
  1326.                 '・インストールの実行\n'+
  1327.                 '・終了',
  1328.             JA:
  1329.                 '日本語',
  1330.             EN:
  1331.                 '英語',
  1332.             NEXT:
  1333.                 '次へ...',
  1334.             CLOSE:
  1335.                 '閉じる',
  1336.             GOTO_START:
  1337.                 '最初に戻る...',
  1338.             CONTINUE:
  1339.                 '続行',
  1340.             ABORT:
  1341.                 '中止',
  1342.             YES:
  1343.                 'はい',
  1344.             NO:
  1345.                 'いいえ',
  1346.             OK:
  1347.                 'OK',
  1348.             CANCEL:
  1349.                 'キャンセル',
  1350.             INSTALL:
  1351.                 'インストール実行',
  1352.             RESTART_NOW:
  1353.                 '今すぐ再起動',
  1354.             TO_LATER:
  1355.                 '後にする・・・',
  1356.             RENAME_TO_ORIGIN_NAME_MSG:
  1357.                 '【%s1】\n\n'+
  1358.                 '選択されているリンクの名前を元に戻します\n'+
  1359.                 'よろしいですか?\n'+
  1360.                 '\n'+
  1361.                 '──────────────────────────\n%s2\n\n',
  1362.             RENAME_TO_ORIGIN_NAME_ITEM:
  1363.                 '"%s1" →\n"%s2"',
  1364.             RENAME_TO_ORIGIN_NAME_ITEM_SAME:
  1365.                 '"%s1" →\n   >>>【同名のためスキップします】',
  1366.             RENAME_LINKS_BY_MSG:
  1367.                 '【%s1】\r\nパッケージ "%s2":リンクに非対応の拡張子が含まれています\r\n\r\nこのパッケージをリネームしますか?',
  1368.             CONFIRM_OPEN_BROWSER_WITH_TOO_MANY_URLS:
  1369.                 '%s1 個の検索用URLをブラウザで開こうとしています。\n\n本当に実行しますか?',
  1370.             ADDLINKTOLINKGRABBER_TIMEOUT:
  1371.                 'リンクグラバーへの再登録でタイムアウトになりましたので、この処理を中断します',
  1372.             ADDLINKSTOLINKGRABBER_IGNORE_DUP_URL:
  1373.                 '再登録するリンクに同一URLが複数あります\n重複したURLを持つリンクは最初のものだけ再登録されます\n\nこのまま処理を続行しますか?',
  1374.             ERROR_INVALID_TRIGGER_TYPE:
  1375.                 'イベントトリガーのタイプが正しく設定されていません\n\n'+
  1376.                 '解決方法:イベントスクリプトの設定から本スクリプトのトリガーに\n'+
  1377.                 '     【ダウンロードリストコンテキストメニュー選択時】か、\n'+
  1378.                 '     【リンクグラバーコンテキストメニュー選択時】を選択し、\n'+
  1379.                 '     リストビュー画面の右クリックのコンテキストメニューから実行してください\n',
  1380.             ERROR_INVALID_VALUE_IN_DISPATCHE_TABLE:
  1381.                 'initDispatchTable()でのディスパッチデーブルで指定されたApp.*メソッドが見つかりません。\n\t%s1:%s2',
  1382.         },
  1383.        
  1384.         // Don't erase 'en'...
  1385.         // 英語
  1386.         'en':
  1387.         {
  1388.             OPEN_EVERYTHING_BY_TITLE                    : 'Everything(Title)',
  1389.             OPEN_EVERYTHING_BY_AUTHOR                   : 'Everything(Author)',
  1390.             OPEN_LINK                                   : 'Open browser',
  1391.            
  1392.             OPEN_JD_CFG_FOLDER                          : 'Open JD Backup Folder',
  1393.             OPEN_SOURCEURL                              : 'Open the Source Page',
  1394.            
  1395.             RENAME_LINKS_BY_PACKAGENAME                 : 'Rename links to package name',
  1396.             RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME : 'Rename package and links to this link name',
  1397.  
  1398.             RENAME_LINK_BY_LINKNAME                     : 'Rename links to this link name',     // 複数のリンクを選択してから右クリックし、右クリック直下のファイル名 (○○) [△△] □□.zip→(○○) [△△] □□を揃える
  1399.             RENAME_LINK_BY_LINK_AUTHOR                  : 'Rename links to this link name(Author)', // (○○) [△△] □□.zip→[△△]部を揃える
  1400.             RENAME_LINK_BY_LINK_TITLE                   : 'Rename links to this link name(Title)',  // (○○) [△△] □□ ~××~第01巻.zip→□□部を揃える
  1401.             RENAME_LINK_BY_LINK_CATEGORY_AUTHOR         : 'Rename links to this link name(Category+Author)',
  1402.             RENAME_LINK_BY_LINK_LONGTITLE               : 'Rename links to this link name(Category+Author+Title)',
  1403.  
  1404.             RENAME_STARTING_BRACKETS_MOVETO_END         : 'Rename to move starting brackets to end',
  1405.             RENAME_REMOVE_TRAILING_BRACKETS             : 'Rename to remove trailed brackets',
  1406.             RENAME_NUMBERING_FORMAT                     : 'Rename Numbering to comic format',
  1407.             RENAME_FULLWIDTH_TO_HALFWIDTH               : 'Rename Full-width characters to Half',
  1408.             RENAME_NORMALIZE                            : 'Rename Normalize',
  1409.             RENAME_URLDECODE                            : 'Rename URL-Decode',
  1410.  
  1411.  
  1412.             RENAME_AUTHOR_SWAP                          : 'Rename Author - swap',   // [○○×△△]→[△△×○○]
  1413.             RENAME_AUTHOR_CHOP                          : 'Rename Author - chop last author',   // [○○×△△×□□]→[○○×△△]
  1414.             RENAME_AUTHOR_X_TO_BATSU                    : 'Rename Author - x to ×',    // [○○x△△]→[○○×△△]
  1415.  
  1416.             RENAME_TO_PACKAGIZER_NAME                   : 'Revert to original name (Packager applied)...',
  1417.             RENAME_TO_ORIGIN_NAME                       : 'Revert to original name...',
  1418.             RENAME_ALIGN_DIGITS_LENGTH                  : 'Rename links to align the digits length',
  1419.            
  1420.             MOVETO_NEWPACKAGE_WITH_FILENAME             : 'Move to new package with this name',
  1421.             MOVE_SAMENAME_PACKAGES_TO_PACKAGE           : 'Merge same packages',
  1422.             ADD_LINKS_TO_LINKGRABBER                    : 'Add to LinkGrabber',
  1423.  
  1424.             SORT_PACKAGES_BY_TITLE_ASCENDING            : 'Sort packages by Title(ASC)',
  1425.             SORT_PACKAGES_BY_TITLE_DESCENDING           : 'Sort packages by Title(DESC)',
  1426.             SORT_PACKAGES_BY_AUTHOR_ASCENDING           : 'Sort packages by Author(ASC)',
  1427.             SORT_PACKAGES_BY_AUTHOR_DESCENDING          : 'Sort packages by Author(DESC)',
  1428.             SORT_PACKAGES_BY_CATEGORY_TITLE_ASCENDING   : 'Sort packages by Category Title(ASC)',
  1429.             SORT_PACKAGES_BY_CATEGORY_TITLE_DESCENDING  : 'Sort packages by Category Title(DESC)',
  1430.             SORT_PACKAGES_BY_CATEGORY_AUTHOR_ASCENDING  : 'Sort packages by Category Author(ASC)',
  1431.             SORT_PACKAGES_BY_CATEGORY_AUTHOR_DESCENDING : 'Sort packages by Category Author(DESC)',
  1432.             SORT_PACKAGES_BY_ADDEDDATE_ASCENDING        : 'Sort packages by Added Date(ASC)',
  1433.             SORT_PACKAGES_BY_ADDEDDATE_DESCENDING       : 'Sort packages by Added Date(DESC)',
  1434.             SORT_PACKAGES_BY_MYRULE_ASCENDING           : 'Sort packages by my Rules(ASC)',
  1435.             SORT_PACKAGES_BY_MYRULE_DESCENDING          : 'Sort packages by my Rules(DESC)',
  1436.  
  1437.             ENABLE_LINKS_MYRULE_SINGLE                  : 'Enable links matched my Rules only (Single)',
  1438.             ENABLE_LINKS_MYRULE_MULTIPLE                : 'Enable links matched my Rules only (Multiple)',
  1439.  
  1440.             RENAME_ADD_TO_BEGIN_REGEXP                  : /^(.+) *>>$/,
  1441.             RENAME_ADD_TO_END_REGEXP                    : /^<< *(.+)$/,
  1442.             ENABLE_LINKS_BY_HOST_ONLY_REGEXP            : /^Enable only *([-\da-zA-Z\.]+\.[\da-z]{2,4})$/,
  1443.             DISABLE_LINKS_BY_HOST_REGEXP                : /^Disable *([-\da-zA-Z\.]+\.[\da-z]{2,4})$/,
  1444.             OPEN_LINKS_BY_HOST_REGEXP                   : /^Open browser *- *([-\da-zA-Z\.]+\.[\da-z]{2,4})$/,
  1445.             RENAME_ADD_TO_BEGIN                         : '%s1 >>',
  1446.             RENAME_ADD_TO_END                           : '<< %s1',
  1447.             ENABLE_LINKS_BY_HOST_ONLY                   : 'Enable only %s1',
  1448.             DISABLE_LINKS_BY_HOST                       : 'Disable %s1',
  1449.             OPEN_LINKS_BY_HOST                          : 'Open browser - %s1',
  1450.             SEARCH_WEB                                  : '%s1',
  1451.  
  1452.  
  1453.             'SUBMENU_RENAME_ALIGN'                      : 'Rename - align',
  1454.             'SUBMENU_RENAME_ADD_REPLACE'                : 'Rename - add/replace',
  1455.             'SUBMENU_ENABLE_DISABLE'                    : 'Enable/Disable',
  1456.             'SUBMENU_SORT'                              : 'Sort',
  1457.             'SUBMENU_MOVE_FILE_BROWSE'                  : 'Move/File/Add',
  1458.             'SUBMENU_SEARCH_WEB'                        : 'Search WEB',
  1459.             'SUBMENU_SEARCH'                            : 'Open/Search/Etc',
  1460.  
  1461.             SEARCH_GOOGLE_AUTHOR        : 'google.com (Author)',
  1462.             SEARCH_GOOGLE_TITLE         : 'google.com (Title)',
  1463.            
  1464.             MERGETOPACKAGEACTION        : 'Move to new Package...',
  1465.             MOVETODOWNLOAD              : 'Add to Download List',
  1466.             MOVETODOWNLOADANDSTART      : 'Start Downloads',
  1467.             MOVETODOWNLOADANDFORCESTART : 'Force Start Downloads',
  1468.             MOVETODOWNLOADAFTERCONFIRM  : 'Add to Download List...',
  1469.             ADDATTOPTOGGLEACTION        : 'Toggle Add to Download List at top',
  1470.  
  1471.             DownloadTableContext    :"Downloads list - Rightclick menu",
  1472.             LinkgrabberContext      :"LinkGrabber list - Rightclick menu",
  1473. //          DownloadTabBottomBar    :"DownloadTabBottomBar",
  1474. //          LinkgrabberTabBottomBar :"LinkgrabberTabBottomBar",
  1475.  
  1476.             SEPERATOR:
  1477.                 'Separator',
  1478.             INSTALLMENU_MESSAGE:
  1479.                 '==========================\n'+
  1480.                 ' Right click menu Install\n'+
  1481.                 '==========================\n\n'+
  1482.                 '%s1\n'+
  1483.                 '_______________________________________\n\n'+
  1484.                 '%s2',
  1485.             INSTALLMENU_INTRO:
  1486.                 'Add an item to the right-click menu\n\n'+
  1487.                 '  Link grabber - right-click menu\n'+
  1488.                 '  Download list - right-click menu\n\n'+
  1489.                 'Note:\n'+
  1490.                 '  The added menu items can be edited later on "Menu Manager".\n'+
  1491.                 '  Create a backup file of each menu before installation.\n'+
  1492.                 '  Backup files can be restored to their previous state by importing them from "Menu Manager".\n',
  1493.             INSTALLMENU_LANG:
  1494.                 '\nSelect the language of the menu item that you want to install.\n\n',
  1495.             INSTALLMENU_MENU:
  1496.                 'Add a menu to\n"%s1" ?\n\n\n'+
  1497.                 '* A backup of the currently used menu will be saved in the /cfg/ folder of JD.',
  1498.             INSTALLMENU_EXECUTE:
  1499.                 'Create a "backup file" of the following menu,\n and then execute "installation".\n\n'+
  1500.                 'Are you sure?\n\n\n'+
  1501.                 '%s1\n'+
  1502.                 '* The menu can be restored to its previous state by importing the backup file from "Menu Managemer".',
  1503.             INSTALLMENU_EBACKUP:
  1504.                 'ERROR: Failed to create the backup file\n\n'+
  1505.                 'Continue installation without backup?\n\n\n\n'+
  1506.                 '%s1',
  1507.             INSTALLMENU_RESTART:
  1508.                 'Instalation have been complete\n\n'+
  1509.                 '"Restart JDownloader" is required to apply the menu\n\n'+
  1510.                 'Restart JD now ?\n\n\n\n'+
  1511.                 '%s1',
  1512.             INSTALLMENU_DONE:
  1513.                 'Instalation is complete\n\n'+
  1514.                 'Finished...\n\n\n\n'+
  1515.                 '%s1',
  1516.             INSTALLMENU_CANCEL:
  1517.                 'Install is canceled\n'+
  1518.                 'Aborted...\n',
  1519.             INSTALLMENU_BACKUP_DONE:
  1520.                 'Success',
  1521.             INSTALLMENU_BACKUP_NO:
  1522.                 'Before Backup',
  1523.             INSTALLMENU_BACKUP_ERROR:
  1524.                 'Failed',
  1525.             INSTALLMENU_INSTALL_DONE:
  1526.                 'Done',
  1527.             INSTALLMENU_INSTALL_NO:
  1528.                 'Yet',
  1529.             INSTALLMENU_INSTALL_ERROR:
  1530.                 'Failed',
  1531.             INSTALLMENU_RESULT:
  1532.                 '[Menu Type]        "%s1 (%s2)"\n'+
  1533.                 '[Install Status]    %s3\n'+
  1534.                 '[Backup Status]  %s4\n'+
  1535.                 '[Backup Path]     "%s5"',
  1536.             INSTALLMENU_INDICATOR_1:
  1537.                 '>',
  1538.             INSTALLMENU_INDICATOR_2:
  1539.                 '  ',
  1540.             INSTALLMENU_INDICATOR_MSG:
  1541.                 ' Confirm to enter or leave install mode\n'+
  1542.                 ' Select the languale of right-click menus\n'+
  1543.                 ' Select right-click menus to install\n'+
  1544.                 ' Execute to install\n'+
  1545.                 ' End',
  1546.             JA:
  1547.                 'Japanese',
  1548.             EN:
  1549.                 'English',
  1550.             NEXT:
  1551.                 'To next...',
  1552.             CLOSE:
  1553.                 'Close',
  1554.             GOTO_START:
  1555.                 'Go to start...',
  1556.             CONTINUE:
  1557.                 'Continue',
  1558.             ABORT:
  1559.                 'Aort',
  1560.             YES:
  1561.                 'YES',
  1562.             NO:
  1563.                 'NO',
  1564.             RESTART_NOW:
  1565.                 'Restart Now',
  1566.             TO_LATER:
  1567.                 'To later...',
  1568.             OK:
  1569.                 'OK',
  1570.             CANCEL:
  1571.                 'Cancel',
  1572.             INSTALL:
  1573.                 'Install',
  1574.             RENAME_TO_ORIGIN_NAME_MSG:
  1575.                 '[%s1]\n\n'+
  1576.                 'Revert to Original Link Name ?\n\n-----------------------------------------------------------------\n%s2\n\n',
  1577.             RENAME_TO_ORIGIN_NAME_ITEM:
  1578.                 '"%s1" -->\n"%s2"',
  1579.             RENAME_TO_ORIGIN_NAME_ITEM_SAME:
  1580.                 '"%s1" -->\n   >>> [ Skip (same name) ]',
  1581.             RENAME_LINKS_BY_MSG:
  1582.                 '[%s1]\r\nPackage "%s2" : Unsupported link\'s file extension  is found\r\n\r\nRename this package ?',
  1583.             CONFIRM_OPEN_BROWSER_WITH_TOO_MANY_URLS:
  1584.                 'You are trying to open %s1 URLs in the browser.\nDo you want to allow execution?',
  1585.             ADDLINKTOLINKGRABBER_TIMEOUT:
  1586.                 'Adding link to Linkgrabber timed out,\nso this action has been aborted.',
  1587.             ADDLINKSTOLINKGRABBER_IGNORE_DUP_URL:
  1588.                 'Some links have same URLs\nIf there are duplicate URLs, only the first link will be added\n\nContinue adding?',
  1589.             ERROR_INVALID_TRIGGER_TYPE:
  1590.                 'Invalid Event Trigger Type\n\n'+
  1591.                 'Solution:Open Setting and "Event Script Setting", so find this script,\n'+
  1592.                 '          choose trigger to "Select Download List Context Menu"\n'+
  1593.                 '          Or "Select LinkGrabber Context Menu",\n'+
  1594.                 '          Execute by Context Menu on the ListView\n',
  1595.             ERROR_INVALID_VALUE_IN_DISPATCHE_TABLE:
  1596.                 'ERROR: Not found App.* method by the dispatch table at initDispatchTable()\n\t%s1:%s2',
  1597.         },
  1598.     };
  1599.    
  1600.     // check resourceTable
  1601.     if (this.config.DEBUG)
  1602.     {
  1603.         // check invalid type
  1604.         keys(r).forEach(function(k)
  1605.         {
  1606.             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]))})) )
  1607.                 throw TypeError('Invalid value in the resource table at initResourceTables()\nThe value must be a string or RegExp.');
  1608.         },this);
  1609.        
  1610.         // check diff resource ID by languege
  1611.         var allkey = {};
  1612.         keys(r).forEach(function(lng){
  1613.             keys(r[lng]).forEach(function(k){allkey[k]=1});
  1614.         });
  1615.        
  1616.         var buf = {};
  1617.         keys(r).forEach(function(lng){
  1618.             var tmp = keys(r[lng]).filter(function(k){return !allkey[k]});
  1619.             if (tmp&&tmp.length!==0) buf[lng] = tmp;
  1620.         });
  1621.         if (keys(buf).length !== 0) alert(buf);
  1622.     }
  1623.    
  1624.     defNoEnumProps(r,{
  1625.         getResource:function(id,lang){return(this.getTable(lang)||this.getTable(LANGUAGE_DEFAULT))[id]||id},
  1626.         hasLanguage:function(lang){return !!this[lang]},
  1627.         getTable:function(lang){return this[lang]},
  1628.         getLanguages:function(){return keys(this)},
  1629.     });
  1630.    
  1631.     this.resource    = r;
  1632. }
  1633.  
  1634.  
  1635. // ResourceTable 初期化の後
  1636. App.initDispatchTable = function()
  1637. {
  1638.     // 普通のメニュー名と対応したメソッド名
  1639.     var r1 = {
  1640.         // 名前は大文字英字と_のみ
  1641.         OPEN_EVERYTHING_BY_TITLE                    : 'openEverythingByTitle',
  1642.         OPEN_EVERYTHING_BY_AUTHOR                   : 'openEverythingByAuthorName',
  1643.         OPEN_LINK                                   : 'openLink',
  1644.         OPEN_JD_CFG_FOLDER                          : 'openJDCfgFolder',
  1645.         OPEN_SOURCEURL                              : 'openSourceURL',
  1646.         RENAME_LINKS_BY_PACKAGENAME                 : 'renameLinksByPackageName',
  1647.         RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME : 'renamePackageAndLinksByContextLinkName',
  1648.         RENAME_LINK_BY_LINKNAME                     : 'renameLinkByLinkName',
  1649.         RENAME_LINK_BY_LINK_AUTHOR                  : 'renameLinkByLinkAuthor',
  1650.         RENAME_LINK_BY_LINK_TITLE                   : 'renameLinkByLinkTitle',
  1651.         RENAME_LINK_BY_LINK_CATEGORY_AUTHOR         : 'renameLinkByLinkCategoryAuthor',
  1652.         RENAME_LINK_BY_LINK_LONGTITLE               : 'renameLinkByLinkLongTitle',
  1653.         RENAME_STARTING_BRACKETS_MOVETO_END         : 'renameStartingBracketsMoveToEnd',
  1654.         RENAME_REMOVE_TRAILING_BRACKETS             : 'renameRemoveTrailingBrackets',
  1655.         RENAME_NUMBERING_FORMAT                     : 'renameNumberingFormat',
  1656.         RENAME_AUTHOR_SWAP                          : 'renameAuthorSwap',
  1657.         RENAME_AUTHOR_CHOP                          : 'renameAuthorChop',
  1658.         RENAME_AUTHOR_X_TO_BATSU                    : 'renameAuthorXtoBatsu',
  1659.         RENAME_TO_PACKAGIZER_NAME                   : 'renameToPackagizerName',
  1660.         RENAME_TO_ORIGIN_NAME                       : 'renameToOriginName',
  1661.         RENAME_ALIGN_DIGITS_LENGTH                  : 'renameAlignDigitsLength',
  1662.         RENAME_FULLWIDTH_TO_HALFWIDTH               : 'renameFullwidthToHalf',
  1663.         RENAME_NORMALIZE                            : 'renameNormalize',
  1664.         RENAME_URLDECODE                            : 'renameURLDecode',
  1665.        
  1666.         MOVETO_NEWPACKAGE_WITH_FILENAME             : 'moveToNewPackageWithFileName',
  1667. //      MOVE_SAMENAME_PACKAGES_TO_PACKAGE           : 'moveSameNamePackagesToPackage',
  1668.         ADD_LINKS_TO_LINKGRABBER                    : 'addLinksToLinkGrabber',
  1669.         SORT_PACKAGES_BY_TITLE_ASCENDING            : 'sortPackagesByTitleAscending',
  1670.         SORT_PACKAGES_BY_TITLE_DESCENDING           : 'sortPackagesByTitleDescending',
  1671.         SORT_PACKAGES_BY_AUTHOR_ASCENDING           : 'sortPackagesByAuthorAscending',
  1672.         SORT_PACKAGES_BY_AUTHOR_DESCENDING          : 'sortPackagesByAuthorDescending',
  1673.         SORT_PACKAGES_BY_CATEGORY_TITLE_ASCENDING   : 'sortPackagesByCategoryTitleAscending',
  1674.         SORT_PACKAGES_BY_CATEGORY_TITLE_DESCENDING  : 'sortPackagesByCategoryTitleDescending',
  1675.         SORT_PACKAGES_BY_CATEGORY_AUTHOR_ASCENDING  : 'sortPackagesByCategoryAuthorAscending',
  1676.         SORT_PACKAGES_BY_CATEGORY_AUTHOR_DESCENDING : 'sortPackagesByCategoryAuthorDescending',
  1677.         SORT_PACKAGES_BY_ADDEDDATE_ASCENDING        : 'sortPackagesByAddedDateAscending',
  1678.         SORT_PACKAGES_BY_ADDEDDATE_DESCENDING       : 'sortPackagesByAddedDateDescending',
  1679.         SORT_PACKAGES_BY_MYRULE_ASCENDING           : 'sortPackagesByMyRuleAscending',
  1680.         SORT_PACKAGES_BY_MYRULE_DESCENDING          : 'sortPackagesByMyRuleDescending',
  1681.         ENABLE_LINKS_MYRULE_SINGLE                  : 'enableLinksMyRuleSingle',
  1682.         ENABLE_LINKS_MYRULE_MULTIPLE                : 'enableLinksMyRuleMultiple',
  1683.     };
  1684.     // r1内にメニュー名が無ければr2
  1685.     // 正規表現マッチングさせるメニュー名と対応したメソッド名
  1686.     var r2 = {
  1687.         RENAME_ADD_TO_BEGIN_REGEXP                  : 'renameAddMenunameToBegin',
  1688.         RENAME_ADD_TO_END_REGEXP                    : 'renameAddMenunameToEnd',
  1689.         DISABLE_LINKS_BY_HOST_REGEXP                : 'disableLinksByHost',
  1690.         ENABLE_LINKS_BY_HOST_ONLY_REGEXP            : 'enableLinksByHostOnly',
  1691.         OPEN_LINKS_BY_HOST_REGEXP                   : 'openLinksOnlyByHost',
  1692.     };
  1693.    
  1694.     // r2でマッチングしなければ、r3で順々に呼び出す
  1695.     // メニュー名と対応したメソッド名
  1696.     // 呼び出されたメソッドがtrueをreturnすればチェーンは止まる
  1697.     var r3 = [
  1698.         {SEARCH_ON_WEB  : 'searchOnWEBSite'},
  1699.     ];
  1700.     // check dispatchTable
  1701.     if (this.config.DEBUG)
  1702.     {
  1703.         if (!this.resource) throw new Error('ERROR: coding error - initDispatchTable() cannot be called before initResourceTable()');
  1704.         keys(r1).forEach(function(k)
  1705.         {
  1706.             if (!isFunction(this[r1[k]]))
  1707.             {
  1708.                 throw new Error(jdFormatString(this.getResource('ERROR_INVALID_VALUE_IN_DISPATCHE_TABLE'),[k,r1[k]]));
  1709.             }
  1710.         },this);
  1711.     }
  1712.    
  1713.    
  1714. //  const PROP_KEY_DISPATCHER = "App.dispatchTable";
  1715. //  var tmp = getProperty(PROP_KEY_DISPATCHER,true);
  1716. //  if (!tmp)
  1717. //  {
  1718. //      tmp = {};
  1719.     var tmp = {};
  1720.         this.resource.getLanguages().forEach(function(lng)
  1721.         {
  1722.             keys(r1).forEach(function(n)
  1723.             {
  1724.                 const translated_name = this.resource.getResource(n, lng);
  1725.                 tmp[translated_name] = r1[n];
  1726.             },this);
  1727.         },this);
  1728.        
  1729. //      setProperty(PROP_KEY_DISPATCHER,tmp,true);
  1730. //  }
  1731.     this.dispatcher = {};
  1732.     this.dispatcher.normal = tmp;
  1733.     this.dispatcher.regexp = r2;
  1734.     this.dispatcher.chain  = r3;
  1735. };
  1736.  
  1737. App.dispatch = function()
  1738. {
  1739.     // テスト実行ボタン経由ならメニューインストールモードに移行
  1740.     if (this.isTestRun())
  1741.         return this.installMenuMode();
  1742.    
  1743.     const event_name = this.name;
  1744.     const selection  = this.getSelection();
  1745.  
  1746.     // dispatch this.dispatcher.normal
  1747.     var method = this[this.dispatcher.normal[event_name]];
  1748.     if (isFunction(method))
  1749.         return method.call(this, event_name, selection);
  1750.    
  1751.     // dispatch this.dispatcher.regexp
  1752.     this.resource.getLanguages().some(function(lng)
  1753.     {
  1754.         keys(this.dispatcher.regexp).some(function(n)
  1755.         {
  1756.             var regexp_name = this.resource.getResource(n, lng);
  1757.             if (isRegExp(regexp_name))
  1758.             {
  1759.                 var r = regexp_name.exec(event_name);
  1760.                 if (!r) return false;
  1761.                 method = this[this.dispatcher.regexp[n]];
  1762.                 if (isFunction(method))
  1763.                     method.call(this, r[1]?r[1].trim():event_name, selection);
  1764.             }
  1765.         },this);
  1766.     },this);
  1767.    
  1768.     // dispatch this.dispatcher.chain
  1769.     // To stop the chain, return true.
  1770.     this.dispatcher.chain.some(function(o)
  1771.     {
  1772.         method = this[o[keys(o)[0]]];
  1773.         if (!isFunction(method)) return false;
  1774.         var result = method.call(this, event_name, selection);
  1775.         return result;
  1776.     },this);
  1777. };
  1778.  
  1779.  
  1780. //-------------------[Start install menu]-------------------//
  1781. App.isTestRun = function()
  1782. {
  1783.     return btnEvents.isTestRun(this.menu)
  1784.         && this.name=='MyMenuButton'
  1785.         && this.icon=='myIconKey'
  1786.         && this.shortCutString=='myShortcut';
  1787. };
  1788.  
  1789. const menusV2 =
  1790. {
  1791.     LinkgrabberContext:"LinkgrabberContext",
  1792.     DownloadTableContext:"DownloadTableContext",
  1793.     LinkgrabberTabBottomBar:"LinkgrabberTabBottomBar",
  1794.     DownloadTabBottomBar:"DownloadTabBottomBar",
  1795. };
  1796. const LGC = menusV2.LinkgrabberContext;
  1797. const DLC = menusV2.DownloadTableContext;
  1798. const menuType={
  1799.     container:'CONTAINER',
  1800.     action:'ACTION',
  1801.     separator:'ACTION',
  1802. };
  1803. const menuClass = {
  1804.     contextmenu :'org.jdownloader.extensions.eventscripter.GenericEventScriptTriggerContextMenuAction',
  1805. //  toolbar     :'org.jdownloader.extensions.eventscripter.GenericEventScriptTriggerToolbarAction',
  1806. //  mainmenu    :'org.jdownloader.extensions.eventscripter.GenericEventScriptTriggerMainmenuAction',
  1807.     separator   :'org.jdownloader.controlling.contextmenu.SeparatorData',
  1808.     container   :'org.jdownloader.controlling.contextmenu.MenuContainer'
  1809. };
  1810.  
  1811. /**
  1812.  * メニューデータを再構築
  1813.  *
  1814.  * @param {menuItem|menuItem[]} menus メニューデータ
  1815.  * @param {string} seperator_name セパレータの名前
  1816.  * @returns {object} 引数で渡されたメニューデータxを再構築した新しいメニューデータ
  1817.  */
  1818. function rebuildMenu(menus,seperator_name)
  1819. {
  1820.     function r(m)
  1821.     {
  1822.         // Set default value
  1823.         if (m.visible  === undefined) m.visible  = true;
  1824.         if (m.shortcut === undefined) m.shortcut = null;
  1825.         if (m.mnemonic === undefined) m.mnemonic = null;
  1826.         if (m.iconKey  === undefined) m.iconKey  = null;
  1827.         if (m.name     === undefined) m.name     = null;
  1828.        
  1829.         // itemsが配列ならサブメニュー
  1830. //      if (isArray(m.items) && m.items.length)
  1831.         if (isArray(m.items))
  1832.         {
  1833.             m.type = menuType.container;
  1834.             if (m.className === undefined)
  1835.                 m.className  = menuClass.container;
  1836.             m.actionData = {};
  1837.             m.items.forEach(function(v,i,l){r(l[i])});
  1838.         }
  1839.         else if (m.name==seperator_name)
  1840.         {
  1841.             m.type       = menuType.separator;
  1842.             m.className  = menuClass.separator;
  1843.             m.actionData = {};
  1844.             m.items      = [];
  1845.         }
  1846.         else
  1847.         {
  1848.             m.type      = menuType.action;
  1849.             m.className = null;
  1850.             m.items     = [];
  1851.             if (m.actionData === undefined) m.actionData = {};
  1852.             if (m.actionData.clazzName === undefined)
  1853.                 m.actionData.clazzName = menuClass.contextmenu;
  1854.         }
  1855.     }
  1856.    
  1857.     var new_menu = simpleClone(menus);
  1858.    
  1859.     if (isArray(new_menu))
  1860.         new_menu.forEach(function(val,idx,list){r(list[idx])});
  1861.     else
  1862.         r(new_menu);
  1863.  
  1864.     return new_menu;
  1865. }
  1866. /**
  1867.  * コンテキストに応じたルートメニューを取得する
  1868.  *
  1869.  * @param {'DownloadTableContext'|'LinkgrabberContext'} context コンテキスト
  1870.  * @returns {menuItem} ルートメニューデータ(JSON)
  1871.  */
  1872. function getRootContextMenu(context)
  1873. {
  1874.     const storage   = 'cfg/menus_v2/'+context;
  1875.     const if_class = 'org.jdownloader.controlling.contextmenu.ContextMenuConfigInterface';
  1876.     return callAPI('config','get',if_class,storage,'menu');
  1877. }
  1878.  
  1879. /**
  1880.  * メニューデータをルートとして、コンテキストに応じたメニューに設定する
  1881.  * (設定の反映にはJDの再起動が必要)
  1882.  *
  1883.  * @param {menuItem} x メニューデータ
  1884.  * @param {'DownloadTableContext'|'LinkgrabberContext'} context コンテキスト
  1885.  * @returns {boolean} 成功か失敗か
  1886.  */
  1887. function setRootContextMenu(root_menu, context)
  1888. {
  1889.     const storage   = 'cfg/menus_v2/'+context;
  1890.     const if_class = 'org.jdownloader.controlling.contextmenu.ContextMenuConfigInterface';
  1891.     return callAPI('config','set',if_class,storage,'menu',root_menu);
  1892. }
  1893.  
  1894. /**
  1895.  * メニューデータをコンテキストに応じたルートメニューに追加して設定する
  1896.  * (設定の反映にはJDの再起動が必要)
  1897.  *
  1898.  * @param {menuItem|menuItem[]} menu_items メニューデータ
  1899.  * @param {'DownloadTableContext'|'LinkgrabberContext'} context コンテキスト
  1900.  * @returns {boolean} 成功か失敗か
  1901.  */
  1902. function installMenu(context, menu_items)
  1903. {
  1904.     /** @type {menuItem} ルートメニュー取得 */
  1905.     var root_container = getRootContextMenu(context);
  1906.     if (!root_container || root_container.type!=menuType.container || !isArray(root_container.items))
  1907.         return false;
  1908.     if (Array.isArray(menu_items))
  1909.         menu_items.forEach(function(menu_item){root_container.items.push(menu_item)});
  1910.     else
  1911.         root_container.items.push(menu_items);
  1912.    
  1913. //  return true;        // for DEBUG
  1914.     return setRootContextMenu(root_container, context);
  1915. }
  1916.  
  1917. function exportMenu(context, output_file)
  1918. {
  1919.     var top_menu = getExportedMenu(context);
  1920.     if (!top_menu) return false;
  1921.     try
  1922.     {
  1923.         writeFile(output_file, JSON.stringify(top_menu,null,4),false);
  1924.     }
  1925.     catch(e)
  1926.     {
  1927.         return false;
  1928.     }
  1929.     return true;
  1930. //  return false;       // for DEBUG
  1931. }
  1932.  
  1933. function getExportedMenu(context)
  1934. {
  1935.     const storage = 'cfg/menus_v2/'+(context||menusV2.DownloadTableContext);
  1936.     const if_class = 'org.jdownloader.controlling.contextmenu.ContextMenuConfigInterface';
  1937.     var exported_menu = {};
  1938.     exported_menu['root']   = callAPI('config','get',if_class,storage,'menu');          // object
  1939.     exported_menu['unused'] = callAPI('config','get',if_class,storage,'unuseditems');   // array
  1940.     return (exported_menu['root']) ? exported_menu : null;
  1941. }
  1942.  
  1943. function getDateTimeMSecString(d)
  1944. {
  1945.     return d.getFullYear()+'-'+zeroPadding(d.getMonth(),2)+'-'+zeroPadding(d.getDate(),2)
  1946.         +'_'+zeroPadding(d.getHours(),2)+zeroPadding(d.getMinutes(),2)+zeroPadding(d.getSeconds(),2)
  1947.         +'_'+zeroPadding(d.getMilliseconds(),3);
  1948. }
  1949.  
  1950. /**
  1951.  * enumAllMenuItems JDのmenu構造のitemを全て列挙
  1952.  *    再帰処理を使わないスタックによる処理
  1953.  *    列挙順序は浅層から深層、一度親を列挙し切ってから子~を列挙する
  1954.  *
  1955.  * @param {object} menus itemを列挙するmenu構造オブジェクト
  1956.  * @param {function} func 各item毎に呼び出されるコールバック関数
  1957.  *                        func(menuItem, parentItemsIndex, parentItems, menus);
  1958.  *                        true相当を返すと列挙を中断する
  1959.  * @param {object} thisObj func関数が呼び出される時に使用するthisオブジェクト
  1960.  *                         nullかundefinedならグローバルスコープでfuncを実行
  1961.  * @returns {boolean} itemを全て列挙しきってループが終了した場合false
  1962.  *                    途中でreturn trueされて中断された場合true
  1963.  *                    Array.prototype.someと似た仕様
  1964.  */
  1965. function enumAllMenuItems(menus,func,thisObj)
  1966. {
  1967.     var result = false, current = null, stack = [menus];
  1968.     while((current=stack.pop()) && !(result=current.some(function(x, i, l)
  1969.     {
  1970.         if (!x) return false;
  1971.         if (func.call(thisObj, x, i, l, menus)) return true;
  1972.         if (isArray(x.items) && 0 < x.items.length)
  1973.             stack.push(x.items);
  1974.     })));
  1975.    
  1976.     return result;
  1977. }
  1978.  
  1979. App.insertMenuNames = function(/*IN OUT*/menus, id, namelist, lang)
  1980. {
  1981.     var names = namelist;
  1982.     if (!isArray(menus)) return false;
  1983.     if (!(names!=null)) return false;
  1984.     if (!(isArray(names)&&names.length))
  1985.     {
  1986.         if (!isObject(names)) return false;
  1987.         names = keys(names);
  1988.         if (0===names.length) return false;
  1989.     }
  1990.    
  1991.     var submenu_name = 'INSERTMENU.'+id;
  1992.     var tpl = this.getResource(id,lang);
  1993.     var items = names.map(function(n){
  1994.         return {name:jdFormatString(tpl,this.getResource(n,lang))}
  1995.     },this);
  1996.    
  1997.     if (enumAllMenuItems(menus, function(item,i,parentItems)
  1998.     {
  1999.         if (item.name != submenu_name) return false;
  2000.         if (item.iconKey)           // iconKeyのデフォルト値があればitemsに設定
  2001.             items.forEach(function(it){it.iconKey=item.iconKey});
  2002.         parentItems.splice(i,1);    // 挿入場所目印のitemを削除
  2003.         Array.prototype.splice.apply(parentItems, [i,0].concat(items)); // parentItems.splice(i,0, ...items)同等、itemsを挿入
  2004.         return true;    // 列挙を停止
  2005.     },this))
  2006.         return true;
  2007.    
  2008.     return 0 !== Array.prototype.push.apply(menus, items);
  2009. };
  2010.  
  2011. App.getMenu = function(lang)
  2012. {
  2013.     // UserConfig内の設定から取得する
  2014.     const insertNameIds = [
  2015.         'RENAME_ADD_TO_END',
  2016.         'RENAME_ADD_TO_BEGIN',
  2017.         'ENABLE_LINKS_BY_HOST_ONLY',
  2018.         'DISABLE_LINKS_BY_HOST',
  2019.         'OPEN_LINKS_BY_HOST',
  2020.         'SEARCH_WEB',
  2021.     ];
  2022.     var separator = this.translation.getSeparatorName();    // JDの言語ファイルから取得
  2023.     var menuItems = simpleClone(this.config.menuConfig.templateMenuItems);
  2024.    
  2025.     // Translate menu names
  2026.     enumAllMenuItems(menuItems, function(item)
  2027.     {
  2028.         if (!item.name) return;
  2029.         item.name = (item.name == '-' && !item.items) ? separator : this.getResource(item.name, lang);
  2030.     }, this);
  2031.    
  2032.     // Insert menu names
  2033.     insertNameIds.forEach(function(id){
  2034.         this.insertMenuNames(menuItems, id, this.config.menuConfig[id], lang);
  2035.     },this);
  2036.    
  2037.     // Rebuild menu
  2038.     return rebuildMenu(menuItems, separator);
  2039. };
  2040.  
  2041. function renameIfFileExists(path)
  2042. {
  2043.     const f = getPath(path);
  2044.     if (!f.exists()) return path;
  2045.     var f_path = f.getParent() + f.getPathSeparator();
  2046.     var f_ext = f.getExtention();
  2047.     var f_name = f.getName();
  2048.     if (f_ext)
  2049.     {
  2050.         f_ext = '.'+f_ext;
  2051.         f_name = f_name.slice(0, f_ext.length*-1);
  2052.     }
  2053.     f_path += f_name;
  2054.     const max = 100;
  2055.     for (var i=1;i<max;i++)
  2056.     {
  2057.         var tmp_path = f_path+'_'+i+f_ext;
  2058.         if (! getPath(tmp_path).exists()) return tmp_path;
  2059.     }
  2060.     return path;
  2061. }
  2062.  
  2063. const mState = {
  2064.     NO:0,
  2065.     DONE:1,
  2066.     ERROR:2,
  2067.     str:function(r){return {0:'NO',1:'DONE',2:'ERROR'}[r||0]},
  2068.     state:function(r){return r?this.DONE:this.ERROR},
  2069.     isOK:function(s){return this.DONE==s},
  2070. };
  2071. function buPath(f,t){return renameIfFileExists(jdPath('cfg\\'+jdFormatString(f,[t])))};
  2072. const C3 = {LinkgrabberContext:'LGC',DownloadTableContext:'DLC'};
  2073. const C7 = {LGC:menusV2.LinkgrabberContext,DLC:menusV2.DownloadTableContext};
  2074. App.installMenuMode = function()
  2075. {
  2076.     const IM = 'INSTALLMENU_';
  2077.     const EXIT = '$exit$';
  2078.     const BUTTON1 = 1;
  2079.     const BUTTON2 = 0;
  2080.     const datetime = getDateTimeMSecString(new Date);
  2081.     const BACKUP_PATH = {DLC:buPath('backup_%s1.jdDLMenu',datetime), LGC:buPath('backup_%s1.jdLGMenu',datetime)};
  2082.     const indicator_list = this.getResource(IM+'INDICATOR_MSG').split(/[\r\n]+/).filter(function(s){return s.trim()});
  2083.     const indicator_1    = this.getResource(IM+'INDICATOR_1');
  2084.     const indicator_2    = this.getResource(IM+'INDICATOR_2');
  2085.     const msg_template   = this.getResource(IM+'MESSAGE');
  2086.    
  2087.     // key:{
  2088.     //   ind:インディケータのindex
  2089.     //   btn1:ボタン1の文字列
  2090.     //   btn2:ボタン2の文字列
  2091.     //   next1:ボタン1を押した時に遷移する先のkey
  2092.     //   next2:ボタン2を押した時に遷移する先のkey
  2093.     //   next:ボタンを押した時に遷移する先のkey
  2094.     //   onBtn1:ボタン1を押した時に呼び出されるCallback(返り値を優先してnext扱い).call(this,pages,page,nextid)
  2095.     //   onBtn2:ボタン2を押した時に呼び出されるCallback(返り値を優先してnext扱い).call(this,pages,page,nextid)
  2096.     //   msg:表示するテキストのResourceIDか文字列
  2097.     //   onMessage:表示するテキストが必要な時に呼び出されるCallback(返り値を優先してmsg扱い).onMessage.call(this,pages,page,nextid);
  2098.     // }
  2099.     // nextでEXIT = '$exit$'を指定すれば終了
  2100.     var pages ={
  2101.         menus:[],
  2102.         mlang:'',
  2103.         intro  :{ind:0,btn1:'NEXT',btn2:'ABORT',next1:'lang',next2:'cancel',
  2104.                 onBtn1:function(ps){ps.mlang=LANGUAGE_JA;ps.lastResult=false;ps.menus=[];
  2105.                 keys(ps).forEach(function(k){delete ps[k].result})}},   // reset
  2106.         lang   :{ind:1,btn1:'JA', btn2:'EN',next:'lgc',
  2107.                 onBtn1:function(ps){ps.mlang=LANGUAGE_JA},onBtn2:function(ps){ps.mlang=LANGUAGE_EN}},
  2108.         lgc    :{ind:2,btn1:'YES',btn2:'NO',msg:['MENU', menusV2.LinkgrabberContext],next:'dlc',
  2109.                 onBtn1:function(ps){ps.menus.push(new String(menusV2.LinkgrabberContext))}},
  2110.         dlc    :{ind:2,btn1:'YES',btn2:'NO',msg:['MENU', menusV2.DownloadTableContext],next:'execute',
  2111.                 onBtn1:function(ps){ps.menus.push(new String(menusV2.DownloadTableContext))},
  2112.                 onBtn2:function(ps){if(ps.menus.length==0)return'cancel'}},
  2113.         execute:{ind:3,btn1:'INSTALL',btn2:'CANCEL',next1:'restart',next2:'cancel',error:'ebackup',
  2114.                 onMessage:function(ps,p){return ['EXECUTE',ps.menus.map(function(c){
  2115.                         return this.jdFormat(IM+'INSTALL_NO',[c,ps.mlang.toUpperCase()])+'\n'
  2116.                     },this).join('')];},
  2117.                 onBtn1:function(ps,p)
  2118.                 {
  2119.                     if (! ps.exportMenu.call(this,ps,p))  return p.error;
  2120.                     if (! ps.installMenu.call(this,ps,p)) return p.next2;
  2121.                 }},
  2122.         ebackup:{ind:3,btn1:'INSTALL',btn2:'ABORT',next1:'restart',next2:'cancel',
  2123.                 onBtn1:function(ps,p)
  2124.                 {
  2125.                     if (! ps.installMenu.call(this,ps,p)) return p.next2;
  2126.                 }},
  2127.         restart:{ind:3,btn1:'RESTART_NOW',btn2:'TO_LATER',next:'done',
  2128.                 onBtn1:function(){callAPI('system','restartJD')}},
  2129.         done   :{ind:4,btn1:'CLOSE',btn2:'GOTO_START',next1:EXIT,next2:'intro'},
  2130.         cancel :{ind:4,btn1:'CLOSE',btn2:'GOTO_START',next1:EXIT,next2:'intro'},
  2131.         installMenu:function(ps,p)
  2132.                 {   // install
  2133.                     ps.menus.forEach(function(c){
  2134.                         c.installed = mState.state(installMenu(c, this.getMenu(ps.mlang,C3[c])))},this);
  2135.                     return !ps.menus.some(function(c){return !mState.isOK(c.installed)});
  2136.                 },
  2137.         exportMenu:function(ps,p)
  2138.                 {   // exports
  2139.                     ps.menus.forEach(function(c){
  2140.                         c.backuped = mState.state(exportMenu(c,BACKUP_PATH[C3[c]]))});
  2141.                     return !ps.menus.some(function(c){return !mState.isOK(c.backuped)});
  2142.                 },
  2143.         // return result as page.msg
  2144.         getResult:function(ps,p,id)
  2145.                 {
  2146.                     return([
  2147.                         id.toUpperCase(),
  2148.                         ps.menus.map(function(c){
  2149.                             var n = IM+('INSTALL_'+mState.str(c.installed));
  2150.                             var b = IM+('BACKUP_' +mState.str(c.backuped ));
  2151.                             return this.jdFormat(IM+'RESULT',
  2152.                                 [c, ps.mlang.toUpperCase(), n, b, BACKUP_PATH[C3[c]]])+'\n'
  2153.                         },this).join('\n')
  2154.                     ]);
  2155.                 },
  2156.     };
  2157.     pages.execute.onMessage =
  2158.     pages.ebackup.onMessage =
  2159.     pages.restart.onMessage =
  2160.     pages.done.onMessage    = pages.getResult;
  2161.  
  2162.     var max_dialog_count = 30;
  2163.     var lastResult = false;
  2164.     var nextid = 'intro';
  2165.     while (nextid != EXIT)
  2166.     {
  2167.         var page = pages[nextid];
  2168.         var msg = page.msg;
  2169.         if (isFunction(page.onMessage)) msg = page.onMessage.call(this,pages,page,nextid);
  2170.         else if (msg===undefined) msg = nextid.toUpperCase();
  2171.         msg = isArray(msg)?msg.concat():[""+msg];
  2172.         msg[0] = IM+msg[0];
  2173.         msg = jdFormatString(msg_template, [this.jdFormat(msg[0], msg.length==1?undefined:msg.slice(1)),
  2174.             indicator_list.map(function(s,i){return(i==page.ind?indicator_1:indicator_2)+s+'\n'}).join('')]);
  2175.         lastResult = page.result = this.confirm(msg, page.btn1, page.btn2);
  2176.         if (max_dialog_count-- <= 0)
  2177.         {
  2178.             alert('ERROR: showConfirmDialog() called over max count.');
  2179.             break;
  2180.         }
  2181.         for(var i=1;i<=2;i++)
  2182.         {
  2183.             if (page.result != (i&1)) continue;
  2184.             if (isFunction(page['onBtn'+i]))
  2185.             {
  2186.                 nextid = page['onBtn'+i].call(this,pages,page,nextid);
  2187.                 if (nextid != null) break;
  2188.             }
  2189.             nextid = page['next'+i] || page['next'] || EXIT;
  2190.             break;
  2191.         }
  2192.     }
  2193. };
  2194. //--------------------[End install menu]--------------------//
  2195.  
  2196.  
  2197. App.init = function()
  2198. {
  2199.     this.config.initConfig();
  2200.    
  2201.     // JD言語設定をリソーステーブルの使用言語に設定
  2202.     // リソーステーブルの言語に無ければ、デフォルトの'en'にする
  2203.     this.script_language = this.translation.language;
  2204.     this.initResourceTables();
  2205.     if (! this.resource.hasLanguage(this.script_language))
  2206.         this.script_language = LANGUAGE_DEFAULT;
  2207.    
  2208.     this.initDispatchTable();
  2209. };
  2210.  
  2211.  
  2212. App.setEventProperty = function(props)
  2213. {
  2214.     this.menu = props.menu;
  2215.     this.name = props.name;
  2216.     this.icon = props.icon;
  2217.     this.shortCutString = props.shortCutString;
  2218.    
  2219.     this.DLC = {
  2220.         contextId:        'DLC',
  2221.         selection:        null,
  2222.         APIName:          'downloadsV2',
  2223.         getAllPackages:   getAllFilePackages,
  2224.         getAllLinks:      getAllDownloadLinks,
  2225.         getPackageByUUID: getDownloadPackageByUUID,
  2226.         getLinkByUUID:    getDownloadLinkByUUID,
  2227.         moveLinks:        API.moveLinks,
  2228.         movePackages:     API.movePackages,
  2229.         movetoNewPackage: API.movetoNewPackage,
  2230.         queryLinks:       API.queryLinks,
  2231.         startOnlineStatusCheck: API.startOnlineStatusCheck,
  2232.     };
  2233.     this.LGC = {
  2234.         contextId:        'LGC',
  2235.         selection:        null,
  2236.         APIName:          'linkgrabberv2',
  2237.         getAllPackages:   getAllCrawledPackages,
  2238.         getAllLinks:      getAllCrawledLinks,
  2239.         getPackageByUUID: getCrawledPackageByUUID,
  2240.         getLinkByUUID:    getCrawledLinkByUUID,
  2241.         moveLinks:        API.moveLinks,
  2242.         movePackages:     API.movePackages,
  2243.         movetoNewPackage: API.movetoNewPackage,
  2244.         queryLinks:       API.queryLinks,
  2245.         startOnlineStatusCheck: API.startOnlineStatusCheck,
  2246.  
  2247.         addLinks:            API.addLinks,
  2248.         moveToDownloadlist:  API.moveToDownloadlist,
  2249.         queryLinkCrawlerJobs:API.queryLinkCrawlerJobs,
  2250.         abort:               API.abort,
  2251.     };
  2252.     /*
  2253.     this.DLC = {
  2254.         contextId:        'DLC',
  2255.         selection:        null,
  2256.         APIName:          'downloadsV2',
  2257.         getAllPackages:   getAllFilePackages,
  2258.         getAllLinks:      getAllDownloadLinks,
  2259.         getPackageByUUID: getDownloadPackageByUUID,
  2260.         getLinkByUUID:    getDownloadLinkByUUID,
  2261.         moveLinks:        function(linkIds,afterLinkID,destPackageID){return callAPI(this.APIName,APIMethod.moveLinks,linkIds,afterLinkID,destPackageID)},
  2262.         movePackages:     function(packageIds,afterDestPackageId){return callAPI(this.APIName,APIMethod.movePackages,packageIds,afterDestPackageId)},
  2263.         movetoNewPackage: function(linkIds,pkgIds,newPkgName,downloadPath){return callAPI(this.APIName,APIMethod.movetoNewPackage,linkIds,pkgIds,newPkgName,downloadPath)},
  2264.         queryLinks:       function(queryParams){return callAPI(this.APIName,APIMethod.queryLinks,queryParams)},
  2265.         startOnlineStatusCheck:function(linkIds,packageIds){return callAPI(this.APIName,APIMethod.startOnlineStatusCheck,linkIds,packageIds)},
  2266.     };
  2267.     this.LGC = {
  2268.         contextId:        'LGC',
  2269.         selection:        null,
  2270.         APIName:          'linkgrabberv2',
  2271.         getAllPackages:   getAllCrawledPackages,
  2272.         getAllLinks:      getAllCrawledLinks,
  2273.         getPackageByUUID: getCrawledPackageByUUID,
  2274.         getLinkByUUID:    getCrawledLinkByUUID,
  2275.         moveLinks:        function(linkIds,afterLinkID,destPackageID){return callAPI(this.APIName,APIMethod.moveLinks,linkIds,afterLinkID,destPackageID)},
  2276.         movePackages:     function(packageIds,afterDestPackageId){return callAPI(this.APIName,APIMethod.movePackages,packageIds,afterDestPackageId)},
  2277.         movetoNewPackage: function(linkIds,pkgIds,newPkgName,downloadPath){return callAPI(this.APIName,APIMethod.movetoNewPackage,linkIds,pkgIds,newPkgName,downloadPath)},
  2278.         queryLinks:       function(queryParams){return callAPI(this.APIName,APIMethod.queryLinks,queryParams)},
  2279.         startOnlineStatusCheck:function(linkIds,packageIds){return callAPI(this.APIName,APIMethod.startOnlineStatusCheck,linkIds,packageIds)},
  2280.  
  2281.         addLinks:         function(query){return callAPI(this.APIName,APIMethod.addLinks,query)},
  2282.         moveToDownloadlist:function(linkIds,packageIds){return callAPI(this.APIName,APIMethod.moveToDownloadlist,linkIds,packageIds)},
  2283.         queryLinkCrawlerJobs:function(query){return callAPI(this.APIName,APIMethod.queryLinkCrawlerJobs,query)},
  2284.         abort:            function(jobId){return callAPI(this.APIName,APIMethod.abort,jobId)},
  2285.     };
  2286.     */
  2287.     this.CTX = this.LGC;    // this.CTX.* is alias to this.LGC.* or this.DLC.* by context
  2288.     if (this.isDLC())
  2289.     {
  2290.         this.DLC.selection = dlSelection;
  2291.         this.CTX = this.DLC;
  2292.     }
  2293.     else if (this.isLGC())
  2294.     {
  2295.         this.LGC.selection = lgSelection;
  2296. //      this.CTX = this.LGC;
  2297.     }
  2298.     else if (this.isTestRun())
  2299.     {
  2300. //      if (getGlobalThis()["dlSelection"])
  2301.         if (getGlobalThis().dlSelection)
  2302.         {
  2303.             this.DLC.selection = dlSelection;
  2304.             this.CTX = this.DLC;
  2305.         }
  2306. //      if (getGlobalThis()["lgSelection"])
  2307.         if (getGlobalThis().lgSelection)
  2308.         {
  2309.             this.LGC.selection = lgSelection;
  2310.             this.CTX = this.LGC;
  2311.         }
  2312.     }
  2313.     else
  2314.     {
  2315.         alert(this.menu);
  2316.         throw new Error();
  2317.     }
  2318. };
  2319.  
  2320. /**
  2321.  * showConfirmDialog() this.getResourceを挟むだけの単純ラッパー
  2322.  * 引数の文字列をthis.getResource(*Id)で置換可能なら置換する
  2323.  */
  2324. App.confirm = function(msgId, yesId, noId){return showConfirmDialog(this.getResource(msgId),this.getResource(yesId),this.getResource(noId))};
  2325.  
  2326. /**
  2327.  * 汎用リネーム関数(1アイテム完結)
  2328.  *     対象は選択されたリンク
  2329.  *
  2330.  * @param {(orig_name:string)=>string} callback_func 『リネーム前の名前』を受け取って
  2331.  *    『リネーム後の名前』を返すコールバック関数
  2332.  *     このコールバック関数は各リンク・パッケージのリネーム前に毎回呼び出される
  2333.  * @param {object[]=getItemsByContextType(App.getSelection()} packages_or_links packageかlinkの配列
  2334.  *      (指定されなければ、右クリックされたのがパッケージかリンクかで切り替える)
  2335.  *
  2336.  */
  2337. App.genericRenamer = function(callback_func)
  2338. {
  2339.     var items = getItemsByContextType(this.getSelection());
  2340.  
  2341.     items.forEach(function(l)
  2342.     {
  2343.         var orig_name = l.getName();
  2344.         var new_name = callback_func.call(this, orig_name);
  2345.         if (new_name && orig_name != new_name)
  2346.             l.setName(new_name);
  2347.     },this);
  2348. };
  2349.  
  2350.  
  2351. /**
  2352.  *  名前の全角英数を半角に置換
  2353.  */
  2354. App.renameFullwidthToHalf=function(event_name, selection)
  2355. {
  2356. //  alert("renameFullwidthToHalf",event_name, selection);
  2357.     this.genericRenamer(function(orig)
  2358.     {
  2359.         var regexp_all_FullWidth_AlphaNum = /[A-Za-z0-9]/g;
  2360.         return orig.replace(
  2361.             regexp_all_FullWidth_AlphaNum,
  2362.             function(m){return String.fromCharCode(m.charCodeAt(0)-0xFEE0)}
  2363.         )
  2364.     });
  2365. };
  2366.  
  2367. /**
  2368.  *  名前の中のURLエンコードされた部分をデコードする
  2369.  */
  2370. App.renameURLDecode = function(event_name, selection)
  2371. {
  2372.     this.genericRenamer(function(orig){return decodeURIComponent(orig)});
  2373. };
  2374.  
  2375. /**
  2376.  *  名前を正規化する
  2377.  */
  2378. App.renameNormalize = function(event_name, selection)
  2379. {
  2380.     this.genericRenamer(function(orig){return normalizeTitle(orig)});
  2381. };
  2382.  
  2383. /**
  2384.  * 文字列の先頭括弧内のカテゴリを置換する
  2385.  *
  2386.  * @param str {string} 置換前の文字列
  2387.  * @param cat {string} 置き換えるカテゴリ文字列
  2388.  * @returns {string} 置換後の文字列
  2389.  */
  2390. function replaceCategory(str, cat)
  2391. {
  2392.     if (!cat) return str;
  2393.  
  2394. //  var regexp_category_and_author = /^ *\((?:一般|同人|成年|18禁|アニメ|BL|官能|少女|ライトノベル|書籍)[^\)]*\)(?: *\[[^\]]+\])? *$/;
  2395.     var regexp_category = /^\((?:一般|同人|成年|18禁|アニメ|BL|官能|少女|ライトノベル|書籍)[^\)]*\) */;
  2396.    
  2397. //  if (!regexp_category_and_author.test(str) || !regexp_category.test(str))
  2398.     if (!regexp_category.test(cat) || !regexp_category.test(str))
  2399.         return cat.trim() + ' ' + str;
  2400.    
  2401.     return str.replace(regexp_category, cat.trim() + ' ');
  2402. }
  2403.  
  2404. /**
  2405.  *  名前の先頭に指定文字列を追加
  2406.  *     ※ (○○) がカテゴリ名っぽかったら、置換モード
  2407.  *
  2408.  *   メニュー名 "(一般小説) >>"
  2409.  *   "[作者名] 作品名.zip" =>  "(一般小説) [作者名] 作品名.zip"
  2410.  *
  2411.  */
  2412. App.renameAddMenunameToBegin = function(event_name, selection)
  2413. {
  2414.     if (! event_name) return;
  2415.     this.genericRenamer(function(orig){return replaceCategory(orig, event_name)});
  2416. };
  2417.  
  2418. /**
  2419.  * 名前の末尾にメニュー名を追加
  2420.  *    ※名前の中にメニュー名があればリネームしない
  2421.  *
  2422.  *  メニュー名 "<< 寄せ集め"
  2423.  *   "[作者名] 作品名.zip" =>  "[作者名] 作品名 寄せ集め.zip"
  2424.  *
  2425.  */
  2426. App.renameAddMenunameToEnd = function(event_name, selection)
  2427. {
  2428.     if (!event_name) return;
  2429.     this.genericRenamer(function(orig)
  2430.     {
  2431.         // 追加する文字列が既に含まれていればスキップ
  2432.         if (orig.indexOf(event_name) != -1) return '';
  2433.         return getFileSpec(orig) + ' ' + event_name + getExt(orig);
  2434.     });
  2435. };
  2436.  
  2437. /**
  2438.  * 名前の後ろから括弧を探して削除
  2439.  */
  2440. App.renameRemoveTrailingBrackets = function(event_name, selection)
  2441. {
  2442.     this.genericRenamer(function(orig){
  2443.         var regexp_bottom_various_brackets = / *(?:\[.+?\]|\(.+?\)|【.+?|(.+?|[.+?)(.*?)$/;
  2444.         return orig_name.replace(regexp_bottom_various_brackets, "$1");
  2445.     });
  2446. };
  2447.  
  2448. /**
  2449.  * 名前の先頭括弧を後方に移動
  2450.  *     入れ子は考慮外
  2451.  */
  2452. App.renameStartingBracketsMoveToEnd = function(event_name, selection)
  2453. {
  2454.     this.genericRenamer(function(orig){
  2455.         const topbrackets_and_otherpart
  2456.             = /^(\(.+?\)|\[.+?\]|【.+?】|(.+?)|[.+?])\) *((.+?)(?:\.[\da-z]{2,8})?\.[\da-z]{2,8})?$/i;
  2457.         return replaceCategory(orig.replace(topbrackets_and_otherpart, "$2 $1$3"), '');
  2458.     });
  2459. };
  2460.  
  2461.  
  2462. function comicNumbering(str)
  2463. {
  2464.     return (isNaN(str)||str.length==2)?str:(str.length==1)?('0'+str):str.replace(/^0+(\d\d)$/,'$1');
  2465. }
  2466. function replaceNums(str)
  2467. {
  2468.     return str.replace(/\d+/g,function(x){return comicNumbering(x);})
  2469. }
  2470.  
  2471. /**
  2472.  * 第○○巻化
  2473.  *    v02→第02巻、v01-03→第01-03巻 へのリネーム
  2474.  *
  2475.  * 【右クリックされた項目】
  2476.  *   パッケージをクリックしていればパッケージ名を対象とする
  2477.  *   リンクをクリックしていればリンク名を対象とする
  2478.  *
  2479.  * 【選択されている項目】
  2480.  *   選択されているパッケージ、リンクの名前をリネーム対象とする
  2481.  */
  2482.  
  2483. App.renameNumberingFormat = function(event_name, selection)
  2484. {
  2485.     this.genericRenamer(function(orig){
  2486.         const anno = {w:' 透かし有り',s:' 寄せ集め',b:' 別スキャン',e:' (完)',A:' 単行本'};
  2487.         var dest = '';
  2488.         var res = [];
  2489.        
  2490.         // 既に「巻」がある
  2491.         if (res = /^(.+?)(第|全)?(\d{1,3}(?:[-+]\d{1,3})*巻.*)$/.exec(orig))
  2492.         {
  2493.             if (res[2]) return '';
  2494.            
  2495.             // 「第」抜け
  2496.             dest = res[1]+'第'+res[3];
  2497.         }   // 「話」
  2498.         else if (res = /^(.+?)[ _]ch?[-\.]?(\d{1,}(?:-\d{1,})?)(.*)$/i.exec(orig))
  2499.             dest = res[1] +' 第'+ replaceNums(res[2]) +'話'+ res[3];
  2500.         else
  2501.         {
  2502.             // "Lv." 誤認識を予め排除
  2503.             const reg_vol_num = /^(.+?[^Ll])(?:(?:v|[vV]ol(?:ume)?|VOL(?:UME)?)[ \.]? *)(\d{1,3}(?:[-+\.]\d{1,3})*)(e?A|[esbw]+)?\b(.*?)$/;
  2504.             const reg_numonly = /^(.+?) (\d{1,3}(?:[-+]\d{1,3})*)(e?A|[esbw]+)?( .+?)?$/;
  2505.            
  2506.             if ( (res = reg_vol_num.exec(orig)) || (res = reg_numonly.exec(orig)) )
  2507. //              ||  (res = /^(.+?) *(?:[vV](?:[oO][lL][ \.]? *)?)?(\d{1,3}(?:[-+\.]\d{1,3})*)(e?A|[esbw]+)?\b(.*)$/.exec(orig)) )
  2508.             {
  2509.                 dest = res[1] +' 第'+ replaceNums(res[2]) +'巻';
  2510.                 if (res[3] && (res[3] in anno))
  2511.                     dest += anno[res[3]];
  2512.                 if (res[4])
  2513.                     dest += res[4];
  2514.                 dest = dest.replace(/  +/g, ' ').trim();
  2515.             }
  2516.         }
  2517.         return dest;
  2518.     });
  2519. };
  2520.  
  2521. /**
  2522.  * []内のスワップ
  2523.  * [○○×△△]→[△△×○○]
  2524.  */
  2525. App.renameAuthorSwap = function(event_name, selection)
  2526. {
  2527.     this.genericRenamer(function(orig){return orig.replace(/\[(.+?)×([^×]+?)\]/, '[$2×$1]')});
  2528. }
  2529.  
  2530. /**
  2531.  * []内の末尾切り捨て
  2532.  * [○○×△△×□□]→[○○×△△]
  2533.  */
  2534. App.renameAuthorChop = function(event_name, selection)
  2535. {
  2536.     this.genericRenamer(function(orig){return orig.replace(/\[(.+?)×(?:[^×]+?)\]/, '[$1]')});
  2537. };
  2538.  
  2539. /**
  2540.  * []内のxを×に変換
  2541.  * [○○x△△]→[○○×△△]
  2542.  */
  2543. App.renameAuthorXtoBatsu = function(event_name, selection)
  2544. {
  2545.     this.genericRenamer(function(orig){return orig.replace(/\[([^×]+?)x([^×]+?)\]/, '[$1×$2]')});
  2546. };
  2547.  
  2548. /**
  2549.  * 名前の数字部分の桁を揃える
  2550.  *
  2551.  * 【右クリックされた項目】---
  2552.  * 【選択されている項目】選択されたリンクの名前の数字部分の桁を揃える
  2553.  *
  2554.  * ※ 最小桁数=2
  2555.  *
  2556.  */
  2557. App.renameAlignDigitsLength = function(event_name, selection)
  2558. {
  2559.  
  2560.     var min_length = 2;                                 // 最小桁数
  2561.     var regexp_name = /^(\D*)(0*(\d+))(.*?)$/i;         // 数字有り文字列
  2562.  
  2563.     // 最大桁数を取得
  2564.     var max_length = min_length;
  2565.     selection.links.forEach(function (l)
  2566.     {
  2567.         var r = getFileSpec(l.getName()).match(regexp_name);
  2568.         if (r && (r[2].length > max_length))
  2569.             max_length = r[2].length;
  2570.     });
  2571.     this.genericRenamer(
  2572.         function(orig)
  2573.         {
  2574.             var r = getFileSpec(orig).match(regexp_name);
  2575.             if (!r) return null;
  2576.             return r[1] + zeroPadding(r[3], max_length) + r[4] + getExtFull(orig);
  2577.         }
  2578.     );
  2579. };
  2580.  
  2581.  
  2582.  
  2583. /**
  2584.  * 汎用リネーム関数(前処理でヒントを取得してのリネーム)
  2585.  *
  2586.  * @param {(contextItemName:string)=>string} funcGetHint
  2587.  *    右クリック直下のアイテムの名前が渡されるので、
  2588.  *    各リンク・パッケージをリネームする際に渡されるヒントを返す
  2589.  *    (右クリック直下のアイテムが無ければ、選択された一番上のアイテム)
  2590.  * @param {(orig_name:string, hint:string)=>string} funcRename
  2591.  *     各リンク・パッケージをリネームする毎に呼び出される(リネーム直前)
  2592.  *     各リンク・パッケージそれぞれの変更前の名前とfuncGetHintで
  2593.  *     返したヒントが渡されるので、変更後の新しい名前を返す
  2594.  */
  2595. App.genericRenamerWithHint = function(funcGetHint, funcRename)
  2596. {
  2597.     var sel = this.getSelection();
  2598.     var items=null;
  2599.     var contextItemName = '';
  2600.     var contextItemUUID = 0;
  2601.  
  2602. //  * 2024/01/20    希にsel.contextPackageが右クリック直下のパッケージではなく、
  2603. //                  最後に選択したパッケージor選択の一番下のパッケージになる場合がある。
  2604. //                  JDの再起動で直るが、スクリプトからでは判別不能のため代替措置は無理。
  2605.  
  2606.     if (sel.isPackageContext())
  2607.     {
  2608.         items = sel.packages;
  2609.         contextItemName = sel.contextPackage.getName();
  2610.         contextItemUUID = sel.contextPackage.UUID;
  2611.     }
  2612.     else if (sel.isLinkContext())
  2613.     {
  2614.         items = sel.links;
  2615.         contextItemName = sel.contextLink.getName();
  2616.         contextItemUUID = sel.contextLink.UUID;
  2617.     }
  2618.     if (! items || items.length <= 1) return;
  2619.     if (contextItemName == '') contextItemName = items[0].getName();
  2620.    
  2621.     var hint = funcGetHint ? ((funcGetHint).call(this, contextItemName)) : contextItemName;
  2622.     if (!hint) return;
  2623.    
  2624.     items.forEach(function(l)
  2625.     {
  2626.         if (l.UUID == contextItemUUID) return;
  2627.        
  2628.         var orig_name = l.getName();
  2629.         var new_name = funcRename ? ((funcRename).call(this, orig_name, hint)) : hint;
  2630.         if (new_name && orig_name != new_name)
  2631.             l.setName(new_name);
  2632.     },this);
  2633. };
  2634.  
  2635. /**
  2636.  * プロパティに設定された値を新しい名前として選択されているリンクをリネーム
  2637.  */
  2638. App.renameToProperty = function(eventname, selection, prop_name)
  2639. {
  2640.     //
  2641.     // 対象列挙
  2642.     //
  2643.     var items = [];
  2644.     selection.links.forEach(function (l)
  2645.     {
  2646.         var original = l.getProperty(prop_name);
  2647.         if (!original) return;
  2648.         items.push({target:l, renameTo:original});
  2649.     },this);
  2650.     if (items.length === 0) return;
  2651.    
  2652.     //
  2653.     // 確認
  2654.     //
  2655.     var msg = this.jdFormat('RENAME_TO_ORIGIN_NAME_MSG',
  2656.         [eventname, items.map(function(x){return this.jdFormat(
  2657.             (x.target.getName() != x.renameTo ? 'RENAME_TO_ORIGIN_NAME_ITEM' : 'RENAME_TO_ORIGIN_NAME_ITEM_SAME'),
  2658.             [x.target.getName(), x.renameTo])},this).join('\n\n')]);
  2659.     if (!this.confirm(msg, 'YES','NO'))
  2660.         return;
  2661.  
  2662.     //
  2663.     // リネーム
  2664.     //
  2665.     items.links.forEach(function (x)
  2666.     {
  2667.         if (x.renameTo && x.target.getName() != x.renameTo)
  2668.             x.target.setName(x.renameTo);
  2669.     },this);
  2670. };
  2671. /*
  2672. App.renameToProperty = function(eventname, selection, prop_name)
  2673. {
  2674.     var items = [];
  2675.     selection.links.forEach(function (l)
  2676.     {
  2677.         var original = l.getProperty(prop_name);
  2678.         if (!original) return;
  2679.        
  2680.         items.push(jdFormatString(this.getResource(
  2681.             l.getName() != original ? 'RENAME_TO_ORIGIN_NAME_ITEM' : 'RENAME_TO_ORIGIN_NAME_ITEM_SAME'
  2682.         ),[l.getName(),original]));
  2683.     },this);
  2684.     if (items.length === 0) return;
  2685.    
  2686.     var msg = this.jdFormat('RENAME_TO_ORIGIN_NAME_MSG', [event_name, items.join('\n\n')]);
  2687.     if (!this.confirm(msg, 'YES','NO'))
  2688.         return;
  2689.    
  2690.     selection.links.forEach(function (l)
  2691.     {
  2692.         var original = l.getProperty(prop_name);
  2693.         if (original && l.getName() != original)
  2694.             l.setName(original);
  2695.     },this);
  2696. };
  2697. */
  2698.  
  2699. /**
  2700.  * リンクを(パッケージャ適用後の)元の名前に戻す
  2701.  *     リンクのプロパティ"PACKAGIZER_NAME"に設定されている名前にリネーム
  2702.  *     ON_PACKAGIZERイベント時のstate=='AFTER'(2度目)で設定されたnameを想定
  2703.  *     別途EventScriptを導入必須
  2704.  */
  2705. App.renameToPackagizerName = function(event_name, selection)
  2706. {
  2707.     this.renameToProperty(event_name, selection, "PACKAGIZER_NAME");
  2708. };
  2709.  
  2710.  
  2711. /**
  2712.  * リンクを(パッケージャ適用前の)元の名前に戻す
  2713.  *     リンクのプロパティ"ORIGIN_NAME"に設定されている名前にリネーム
  2714.  *     ON_PACKAGIZERイベント時のstate=='BEFORE'(1度目)で設定されたnameを想定
  2715.  *     別途EventScriptを導入必須
  2716.  */
  2717. App.renameToOriginName = function(event_name, selection)
  2718. {
  2719.     this.renameToProperty(event_name, selection, "ORIGIN_NAME");
  2720. };
  2721.  
  2722.  
  2723. /**
  2724.  * 名前を揃える
  2725.  *
  2726.  * 【クリックされたファイルの名前】で、【選択されているファイルの名前】を揃える
  2727.  *
  2728.  * パッケージ名同士を揃える必要はない、パッケージを跨いで名前を揃える必要もないため
  2729.  * 同一パッケージ内のリンクのみ対象
  2730.  * 一つのパッケージ内でのみ有効
  2731.  */
  2732. App.renameLinkByLinkName = function(event_name, selection)
  2733. {
  2734.     if (selection.isPackageContext()) return;       // パッケージがクリックされているか
  2735.     if (1 != selection.packages.length) return;     // 2つ以上のパッケージが選択されているか
  2736.     if (! selection.packages[0].expanded) return;   // パッケージが閉じているか
  2737.    
  2738.     this.genericRenamerWithHint(null, null);
  2739. };
  2740.  
  2741.  
  2742. /**
  2743.  * ファイル名[作者名]を揃える
  2744.  *
  2745.  * 【クリックされたファイルの[]内】で、【選択されているファイルの[]内】を揃える
  2746.  *
  2747.  */
  2748. App.renameLinkByLinkAuthor = function(event_name, selection)
  2749. {
  2750. // ( ()先頭括弧 繰り返し ) ( [] 繰り返し)
  2751.     var regexp_name
  2752.         = /^((?:\([^\(\)]+\) *)*)((?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*)(.+?)$/i;
  2753.     this.genericRenamerWithHint(
  2754.         function(orig){var r=orig.match(regexp_name);return (r&&r[2])?r[2]:null},
  2755.         function(orig, hint){return orig.replace(regexp_name,function(m,m1,m2,m3){return (m1?(m1.trim()+' '):'')+hint.trim()+' '+m3.trim()})}
  2756.     );
  2757. };
  2758.  
  2759. /**
  2760.  * ファイル名(カテゴリ) [作者名]を揃える
  2761.  *
  2762.  * 【クリックされたファイルの(○○) [△△]】で、【選択されているファイルの(○○) [△△]】を揃える
  2763.  * 無ければ(○○) [△△]を追加する
  2764.  *
  2765.  */
  2766. App.renameLinkByLinkCategoryAuthor = function(event_name, selection)
  2767. {
  2768. // ( ()先頭括弧 繰り返し ) ( [] 繰り返し)
  2769.     var regexp_name
  2770.         = /^((?:\([^\(\)]+\) *)*)((?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*)(.+?)$/i;
  2771.     this.genericRenamerWithHint(
  2772.         function(orig){var r=orig.match(regexp_name);return (r&&r[1]&&r[2])?(r[1].trim()+' '+r[2].trim()):null},
  2773.         function(orig, hint){return orig.replace(regexp_name,function(m,m1,m2,m3){return hint+' '+m3.trim()})}
  2774.     );
  2775. };
  2776.  
  2777. /**
  2778.  * ファイル名 タイトルを揃える
  2779.  *
  2780.  * 【クリックされたファイルの(○○) [△△] □□の□□部分】で、【選択されているファイルの□□】を揃える
  2781.  *
  2782.  *
  2783.  * 巻数話数があるものだけ
  2784.  */
  2785. App.renameLinkByLinkTitle = function(event_name, selection)
  2786. {
  2787.     var regexp_name
  2788.         = /^((?:\([^\(\)]+\) *)*(?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*)(.+?) *((?:v(?:ol)?[-\._]?\d+|No[-\._]\d+|[第全]\d+|ch?[-\._]?\d+|[\(\[]| *透かし[あ有]り | *(?:雑誌)?寄せ集め| *別スキャン| *単行本| 20\d\d[-年]?\d+(?:-\d+)?月?号?| \d+).*)(?:\.part\d+\.rar|\.zip|\.[0-9a-z]{2,6})?$/i;
  2789.     this.genericRenamerWithHint(
  2790.         function(orig){var r=orig.match(regexp_name);return r?r[2]:null;},
  2791.         function(orig, hint){return orig.replace(regexp_name,function (m,m1,m2,m3){return m1.trim()+' '+hint.trim()+' '+m3.trim();});}
  2792.     );
  2793. };
  2794.  
  2795. /**
  2796.  * ファイル名 (カテゴリ)[作者名]タイトルまで揃える
  2797.  *
  2798.  * 【クリックされたファイルの(○○) [△△] □□】で、【選択されているファイル】を揃える
  2799.  *
  2800.  *
  2801.  * 巻数話数があるものだけ
  2802.  */
  2803. App.renameLinkByLinkLongTitle = function(event_name, selection)
  2804. {
  2805.     var regexp_name
  2806.         = /^((?:\([^\(\)]+\) *)*(?:\[[^\[\]]*(?:\[[^\[\]]+\][^\[\]]*)*\] *)*.+?) *((?:v(?:ol)?[-\._]?\d+|No[-\._]\d+|[第全]\d+|ch?[-\._]?\d+|[\(\[]| *透かし[あ有]り | *(?:雑誌)?寄せ集め| *別スキャン| *単行本| 20\d\d[-年]?\d+(?:-\d+)?月?号?| \d+).*)(?:\.part\d+\.rar|\.zip|\.[0-9a-z]{2,6})?$/i;
  2807.     this.genericRenamerWithHint(
  2808.         function(orig){var r=orig.match(regexp_name);return r?r[1]:null;},
  2809.         function(orig, hint){return orig.replace(regexp_name,function(m,m1,m2){return hint.trim()+' '+m2.trim()});}
  2810.     );
  2811. };
  2812.  
  2813. //
  2814. // [パッケージ内リネーム系]
  2815. //
  2816.  
  2817. // 渡されたリンクの配列の中で、同一パッケージがないリンクの配列を返す
  2818. function getLinksOnlySingleLink(ls)
  2819. {
  2820.     var pu = {};
  2821.     ls.forEach(function(l){pu[l.package.UUID]=(pu[l.package.UUID]||0)+1});
  2822.     return ls.filter(function(l){return 1 === pu[l.package.UUID]});
  2823. }
  2824.  
  2825. // 渡されたリンクの配列→{パッケージUUID:[Link1,Link2, ...]}
  2826. function getLinksPackageStruct(ls)
  2827. {
  2828.     return ls.reduce(function(a,l){a[l.package.UUID]?a[l.package.UUID].push(l):(a[l.package.UUID]=[l]);return a},{});
  2829. }
  2830.  
  2831. function findExt(p){var ext='';return p.downloadLinks.some(function(l){return ext=getExt(l.getName())}),ext}
  2832.  
  2833. /**
  2834.  * このリンクの名前でパッケージ内をリネーム
  2835.  *
  2836.  * 【右クリックされた項目】--- (複数パッケージを同時処理するため)
  2837.  *
  2838.  * 【選択されている項目】
  2839.  *     『選択されたリンクのファイル名』で、パッケージ名と
  2840.  *     そのパッケージ内全リンクの名前を揃える。
  2841.  *
  2842.  * ※ 各パッケージ内で「リンクは一つだけ選択」する。
  2843.  *    二つ以上のリンクが選択されたパッケージはリネームしない。
  2844.  *    拡張子は維持。
  2845.  *
  2846.  * ※ デフォルトパッケージ名『様々なファイル』『任意』のパッケージはリネームしない。
  2847.  *    リネームしたければパッケージ名を先に変更しておく。
  2848.  *
  2849.  * ※ App.config.RENAME_ALIGN_FILEEXTENSION で設定されているファイル拡張子以外
  2850.  *    のリンクを含む場合は、そのパッケージは処理をスキップする。
  2851.  *    (希に連番画像ファイルを同じファイル名にリネームする事故が起こるため……)
  2852.  */
  2853. App.renamePackageAndLinksByContextLinkName = function(event_name, selection)
  2854. {
  2855.     const DEFAULT_EXTENSION = this.config.DEFAULT_EXTENSION;
  2856.     const RENAME_ALIGN_FILEEXTENSION = this.config.RENAME_ALIGN_FILEEXTENSION;
  2857.    
  2858.     var is_ext_ok = new RegExp('^(?:|.*\.(?:'+RENAME_ALIGN_FILEEXTENSION.join('|')+'))$','i');
  2859.    
  2860.     getLinksOnlySingleLink(selection.links).forEach(function(orig_l)
  2861.     {
  2862.         var p = orig_l.package;
  2863.         var new_name = getFileSpec(orig_l.getName());
  2864.  
  2865.         // リネーム元リンクの名前が無い || デフォルトパッケージ名ならスキップ
  2866.         if (! new_name || this.isAutoCreatedPackageName(p.getName())) return;
  2867.        
  2868.         // 拡張子無しに付与するデフォルト拡張子を取得
  2869.         var default_ext = getExt(orig_l.getName()) || findExt(p) || DEFAULT_EXTENSION;
  2870.  
  2871.         // 許容する拡張子以外を含んでいればパッケージスキップ確認
  2872.         // 連番画像ファイルなど事故防止
  2873.         var is_bad_ext = p.downloadLinks.some(function(x){return !is_ext_ok.test(getExt(x.getName()))});
  2874.         if (is_bad_ext && !this.confirm(
  2875.                 this.jdFormat('RENAME_LINKS_BY_MSG',['RENAME_PACKAGE_AND_LINKS_BY_CONTEXTLINKNAME',p.getName()]),
  2876.                 'YES','NO'))
  2877.             return;
  2878.  
  2879.         // パッケージをリネーム
  2880.         p.setName(new_name);
  2881.  
  2882.         // パッケージ内の全リンクをリネーム
  2883.         p.downloadLinks.forEach(function(l)
  2884.         {
  2885.             //リネーム元リンクをスキップ
  2886.             if (l.UUID == orig_l.UUID) return;
  2887.            
  2888.             // リンクをリネーム
  2889.             l.setName( new_name + (getExtFull(l.getName())||default_ext) );
  2890.         });
  2891.     },this);
  2892. };
  2893.  
  2894. /**
  2895.  * パッケージ名で名前を揃える
  2896.  *    『パッケージ名』で『パッケージ内全ファイルの名前』を揃える
  2897.  *     /^様々なファイル/ なパッケージではリネームしない
  2898.  *
  2899.  * 【右クリックされた項目】---
  2900.  *
  2901.  * 【選択されている項目】
  2902.  *     リンクが選択された場合、親パッケージが対象になる
  2903.  *
  2904.  * ※ デフォルトパッケージ名『様々なファイル』『任意』のパッケージはリネームしない。
  2905.  *    リネームしたければパッケージ名を先に変更しておく。
  2906.  *
  2907.  * ※ App.config.RENAME_ALIGN_FILEEXTENSION で設定されているファイル拡張子以外
  2908.  *    のリンクを含む場合は、そのパッケージは処理をスキップする。
  2909.  *    (希に連番画像ファイルを同じファイル名にリネームする事故が起こるため……)
  2910.  */
  2911. App.renameLinksByPackageName = function(event_name, selection)
  2912. {
  2913.     const DEFAULT_EXTENSION = this.config.DEFAULT_EXTENSION;
  2914.     const RENAME_ALIGN_FILEEXTENSION = this.config.RENAME_ALIGN_FILEEXTENSION;
  2915.    
  2916.     var is_ext_ok = new RegExp('^(?:|.*\.(?:'+RENAME_ALIGN_FILEEXTENSION.join('|')+'))$','i');
  2917.  
  2918.     selection.packages.forEach(function(p)
  2919.     {
  2920.         var new_name = p.getName();
  2921.        
  2922.         // パッケージ名無しか、デフォルトパッケージ名ならスキップ
  2923.         if (! new_name || this.isAutoCreatedPackageName(p.getName())) return;
  2924.  
  2925.         // 拡張子無しに付与するデフォルト拡張子を取得
  2926.         var default_ext = findExt(p) || DEFAULT_EXTENSION;
  2927.        
  2928.         // 許容する拡張子以外を含んでいればパッケージスキップ確認
  2929.         // 連番画像ファイルなど事故防止
  2930.         var is_bad_ext = p.downloadLinks.some(function(x){return !is_ext_ok.test(getExt(x.getName()))});
  2931.         if (is_bad_ext && !this.confirm(
  2932.                 this.jdFormat('RENAME_LINKS_BY_MSG',['RENAME_LINKS_BY_PACKAGENAME',p.getName()]),
  2933.                 'YES','NO'))
  2934.             return;
  2935.        
  2936.         // パッケージ内の全リンクをリネーム
  2937.         p.downloadLinks.forEach(function(l)
  2938.         {
  2939.             // リンクをリネーム
  2940.             l.setName( new_name + (getExtFull(l.getName())||default_ext) );
  2941.         });
  2942.     },this);
  2943. };
  2944.  
  2945. /**
  2946.  * ファイル名から作者名またはタイトルを返す
  2947.  *
  2948.  * @param {string} filename ファイル名
  2949.  * @param {number} part 部分を指定する
  2950.  * @param {[string='|']} delim 該当するものが複数だった場合に、結合するための区切り文字
  2951.  * @returns {string} 指定された部分を切り出して返す
  2952.  */
  2953. function getKeywordFromFilename(filename, part, delim)
  2954. {
  2955.     var search_word = '';
  2956.     var r = filename.match(/^(?:(?:\([^\(\)]+?\) *)*(?:\[([^\]]+)\] *)?)?((?:(?:COMIC|\u30B3\u30DF\u30C3\u30AF) *)?([a-z]+(?: [a-z]+?)*?(?:$|\.|(?= v\d+))|(?:[-+ a-z]+)+|[^ -]+))/i);
  2957.     if (r)
  2958.         if (part == findBy.title
  2959.             || (r[1] && (r[1] == '\u30A2\u30F3\u30BD\u30ED\u30B8\u30FC'
  2960.             || r[1] == '\u96D1\u8A8C')))
  2961.             search_word = r[2].replace(/(?:[!?!?。、]+)$/,'');//検索ワードの末尾感嘆符などは不要
  2962.         else if (r[1])
  2963.         {
  2964.             if (part == findBy.author)
  2965.                 search_word = r[1].split(/\u00D7|\uFF06|\uFF0F/).join((delim===undefined) ? '|' : delim);
  2966.             else if (part == findBy.author_1st)
  2967.                 search_word = (r[1].split(/\u00D7|\uFF06|\uFF0F/))[0];
  2968.         }
  2969.     return(search_word.trim());
  2970. }
  2971.  
  2972. /**
  2973.  * ブラウザを開く個数が設定上限を超えているかどうか、
  2974.  *     超えていた場合、確認メッセージボックスを出して
  2975.  *     制限を超えて開くかどうかYES/NOの結果をboolで返す
  2976.  *
  2977.  * @param {number} url_count 上限数
  2978.  * @returns {boolean} true=YES/false=NO|cancel
  2979.  */
  2980. App.confirmAboutOpenBrowserCount = function(url_count)
  2981. {
  2982.     const MAX_OPEN = this.config.OPEN_BROWSER_MAX;
  2983.     if (url_count == 0) return false;
  2984.     if (url_count <= MAX_OPEN) return true;
  2985.    
  2986.     return this.confirm(
  2987.         this.jdFormat('CONFIRM_OPEN_BROWSER_WITH_TOO_MANY_URLS', [url_count]),
  2988.         'OK',
  2989.         'CANCEL'
  2990.     );
  2991. };
  2992.  
  2993. App.internalOpenUrls = function(urls)
  2994. {
  2995.     const INTERVAL_TIME = this.config.OPEN_BROWSER_INTERVAL;
  2996.     const BROWSER_PATH = this.config.path.browser;
  2997.    
  2998.     if (!isArray(urls)) return;
  2999.     var us = urls.filter(xDup);
  3000.     if (us.length === 0) return 0;
  3001.     if (!this.confirmAboutOpenBrowserCount(us.length)) return;
  3002.     us.forEach(function(url,i)
  3003.     {
  3004.         if (i) sleep(INTERVAL_TIME);
  3005.         callSync( BROWSER_PATH, DQ(url) );
  3006.     });
  3007.     return us.length;
  3008. };
  3009.  
  3010. /**
  3011.  * WEBブラウザで検索
  3012.  *
  3013.  * 選択されたリンクのファイル名から検索ワードを取得して、重複を取り除いた後、ブラウザで検索
  3014.  *
  3015.  *
  3016.  * ESL連想配列に
  3017.  * {"メニュー名":["URL(%REP%をキーワードに置換して引数として渡す)", position(findBy.author|findBy.title)]}
  3018.  *   メニュー名は、リストビューのコンテキストメニュー(右クリックメニュー)での名前
  3019.  *   URLは、"https://www.google.com/?q=%REP%"で、%REP%を検索ワードに置換してブラウザに渡す
  3020.  *   positionは、ファイル名から取得する検索ワードの位置。
  3021.  */
  3022. App.searchOnWEBSite = function(event_name, selection)
  3023. {
  3024.     const search_table = this.config.SEARCH_WEB;
  3025.     if (! search_table[event_name])
  3026.     {
  3027.         // テーブル内にevent_nameが無い場合、
  3028.         // テーブル内にリソース名があれば全言語に変換してチェック
  3029.         if (!keys(search_table).some(function(n)
  3030.         {
  3031.             // リソーステーブルのKEY名ではない文字を含んでいれば次へ
  3032.             if (!/^[\dA-Z_\.]+$/.test(n)) return false;
  3033.            
  3034.             return this.resource.getLanguages().some(function(lng)
  3035.             {
  3036.                 if (this.getResource(n,lng) != event_name) return false;
  3037.                 event_name = n;
  3038.                 return true;
  3039.             },this);
  3040.         },this))
  3041.             return false;
  3042.     }
  3043.    
  3044.     var parttype = search_table[event_name][0];
  3045.     var template_url = (search_table[event_name][1] || ('https://'+event_name+'/?s=%REP%'));
  3046.     var items = [];
  3047.    
  3048.     // 選択されたリンク.親が展開されているか?そのリンクから検索ワード:親の名前から検索ワード
  3049.     selection.links.forEach(function (l)
  3050.     {
  3051.         const n = l.package.expanded ? l.getName() :l.package.getName();
  3052.         if (n == '' || this.isDefaultPackageName(n))
  3053.             return;
  3054.         var search_word = getKeywordFromFilename(n, parttype, ' ');
  3055.         if (parttype != findBy.title && search_word == '')
  3056.             search_word = getKeywordFromFilename(n, findBy.title);
  3057.        
  3058.         if (search_word != '') items.push(template_url.replace("%REP%", search_word));
  3059.     }, this);
  3060.  
  3061.     return this.internalOpenUrls(items);
  3062. };
  3063.  
  3064. /**
  3065.  * 指定リンク(l)のURLがホスト名パターンの配列(pat)にマッチするか否か
  3066.  *
  3067.  * @param {object} l リンク
  3068.  * @param {RegExp[]} pat ホスト名にマッチングさせる正規表現オブジェクトの配列
  3069.  * @return {boolean} true=マッチした、false=マッチしなかった
  3070.  */
  3071. function isLinkURLMatched(l,pat)
  3072. {
  3073.     return pat.some(function(m){return m.test(getxUrl(l))})
  3074. }
  3075.  
  3076. /**
  3077.  * 指定パッケージ(pack)内のホスト名パターン(pattern)にマッチするリンクを有効/無効(enabled)にする
  3078.  *(マッチしないパッケージでは何もしない)
  3079.  *
  3080.  * @param {object} p パッケージ
  3081.  * @param {RegExp|RegExp[]} pattern ホスト名にマッチングさせる正規表現オブジェクトもしくはその配列
  3082.  * @param {boolean=true} enabled true=有効,false=無効
  3083.  * @return {boolean} true=マッチした、false=マッチしなかった
  3084.  */
  3085. App.setEnablePackageByPattern = function(p, pattern, enabled)
  3086. {
  3087.     const patList = isArray(pattern)?pattern:[pattern];
  3088.     if (enabled===undefined) enabled = true;
  3089.    
  3090.     // デフォルトパッケージ名、パターンにマッチしないパッケージはスキップ
  3091.     if (this.isDefaultPackageName(p.getName()) || ! p.downloadLinks.some(function(l){return isLinkURLMatched(l, patList)}))
  3092.         return false;
  3093.    
  3094.     // パターンにマッチするか否かでリンクを有効/無効
  3095.     p.downloadLinks.forEach(function(l){l.setEnabled(isLinkURLMatched(l, patList) ? !!enabled : !enabled)});
  3096.     return true;
  3097. };
  3098.  
  3099. /**
  3100.  * 優先ホスト(単一)のみを有効化する
  3101.  *
  3102.  * 【右クリックされた項目】---
  3103.  * 【選択されている項目】選択パッケージ内のリンクをルールに合わせて有効/無効化する
  3104.  *
  3105.  */
  3106. App.enableLinksMyRuleSingle = function(event_name, selection)
  3107. {
  3108.     selection.packages.forEach(function(p) {    // パターンリスト上位からマッチング
  3109.         this.config.priorityHostRule.some(function(pat){return this.setEnablePackageByPattern(p, pat)},this)
  3110.     },this);
  3111. };
  3112.  
  3113. /**
  3114.  * 選択されたパッケージ内でホスト名パターン(pattern)にマッチするリンクを有効/無効(enabled)にする
  3115.  *(マッチしないパッケージでは何もしない)
  3116.  *
  3117.  * @param {RegExp|RegExp[]} pattern ホスト名にマッチングさせる正規表現オブジェクトもしくはその配列
  3118.  * @param {boolean=true} enabled true=有効,false=無効
  3119.  */
  3120. App.setEnableSelectedPackagesByPattern = function(selection, pattern, enabled)
  3121. {
  3122.     selection.packages.forEach(function(p){this.setEnablePackageByPattern(p, pattern, enabled)},this)
  3123. }
  3124.  
  3125. /**
  3126.  * 優先ホスト(複数)のみを有効化する
  3127.  *
  3128.  * 【右クリックされた項目】---
  3129.  * 【選択されている項目】選択パッケージ内のリンクをルールに合わせて有効/無効化する
  3130.  *
  3131.  */
  3132. App.enableLinksMyRuleMultiple = function(event_name, selection)
  3133. {
  3134.     this.setEnableSelectedPackagesByPattern(selection, this.config.priorityHostRule);
  3135. };
  3136.  
  3137. /**
  3138.  * 指定ホストを無効化する(hogehoge.comを無効化する)
  3139.  *
  3140.  * 【右クリックされた項目】---
  3141.  * 【選択されている項目】選択パッケージ内の指定したホスト名にマッチしたリンクを無効化する
  3142.  *
  3143.  */
  3144. App.disableLinksByHost = function(event_name, selection)
  3145. {
  3146.     const regexp = new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/');
  3147.  
  3148.     selection.links.forEach(function(l)
  3149.     {
  3150.         if (regexp.test(getxUrl(l)))
  3151.             l.setEnabled(false);
  3152.     },this);
  3153. //  this.setEnableSelectedPackagesByPattern(selection, new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/'), false);
  3154. };
  3155.  
  3156. /**
  3157.  * 指定ホストのみを有効化する(他は無効化する)
  3158.  *
  3159.  * 【右クリックされた項目】---
  3160.  * 【選択されている項目】選択パッケージ内の指定したホスト名にマッチしたリンク以外を無効化する
  3161.  *
  3162.  */
  3163. App.enableLinksByHostOnly = function(event_name, selection)
  3164. {
  3165.     this.setEnableSelectedPackagesByPattern(selection, new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/'), true);
  3166. };
  3167.  
  3168.  
  3169. /**
  3170.  * ブラウザで開く
  3171.  *
  3172.  * 【右クリックされた項目】---
  3173.  * 【選択されている項目】リンクのcontentURLをブラウザで開く
  3174.  *
  3175.  * RegExp url_regexp = (NULL | 正規表現オブジェクト) マッチングするURLのみを開く
  3176.  */
  3177. App.openLink = function(event_name, selection)
  3178. {
  3179.     this.openLinkByPattern(selection);
  3180. };
  3181.  
  3182. App.openLinkByPattern = function(selection, url_regexp)
  3183. {
  3184.     var items = selection.links.map(getxUrl);
  3185.     if (isRegExp(url_regexp))
  3186.         items = items.filter(function(url){return url_regexp.test(url)});
  3187.     this.internalOpenUrls(items);
  3188. };
  3189.  
  3190. /**
  3191.  * 指定されたホストを含むリンクのみをブラウザで開く
  3192.  *
  3193.  * 【右クリックされた項目】---
  3194.  * 【選択されている項目】event_nameで指定されたホスト名をcontentURLに含むリンクのみブラウザで開く
  3195.  */
  3196. App.openLinksOnlyByHost = function(event_name, selection)
  3197. {
  3198.     this.openLinkByPattern(selection, new RegExp('^(?:ht|f)tps?://[^/]*' + event_name.replace(/\./g,'\\.').trim() + '/'));
  3199. };
  3200.  
  3201. /**
  3202.  * 選択されたリンクの登録元ページをブラウザで開く
  3203.  *
  3204.  * 【右クリックされた項目】---
  3205.  * 【選択されている項目】リンクのプロパティSOURCE_URLに設定されているURLをブラウザで開く
  3206.  */
  3207. App.openSourceURL = function(event_name, selection)
  3208. {
  3209.     const prop_name = 'SOURCE_URL';
  3210.     var regexp_is_url_ok = /^https?:\/\//;
  3211.     this.internalOpenUrls(
  3212.         selection.links.reduce(function(a,l){
  3213.             var u=l.getProperty(prop_name);
  3214.             u && regexp_is_url_ok.test(u) && a.push(u);
  3215.             return a;
  3216.         },[])
  3217.     );
  3218. };
  3219.  
  3220.  
  3221.  
  3222. /**
  3223.  * Everythingで検索(作者名)
  3224.  *
  3225.  * 【右クリックされた項目】パッケージorリンクの名前から最初の[]内の単語をEverythingで検索
  3226.  * 【選択されている項目】---
  3227.  *
  3228.  * ※ 複数項目は不要
  3229.  */
  3230. App.openEverythingByAuthorName = function(event_name, selection)
  3231. {
  3232.     this.openEverything(selection, findBy.author)
  3233. };
  3234.  
  3235. /**
  3236.  * Everythingで検索(タイトル)
  3237.  *
  3238.  * 【右クリックされた項目】パッケージorリンクの名前から最初の[]の後ろの単語をEverythingで検索
  3239.  * 【選択されている項目】---
  3240.  */
  3241. App.openEverythingByTitle = function(event_name, selection)
  3242. {
  3243.     this.openEverything(selection, findBy.title)
  3244. };
  3245.  
  3246. App.openEverything = function(selection, part)
  3247. {
  3248.     const contextItem = getContextItemByContextType(selection);
  3249.     if (!contextItem) return;
  3250.    
  3251.     const search_word = getKeywordFromFilename(getFileSpec(contextItem.getName()), part||findBy.author);
  3252.     if (!search_word) return;
  3253.    
  3254.     callSync( this.config.path.everything, '-s', DQ(search_word) );
  3255. };
  3256.  
  3257. /**
  3258.  * 同じ名前のパッケージをまとめる
  3259.  *
  3260.  *
  3261.  * 【右クリックされた項目】---
  3262.  *
  3263.  * 【選択されている項目】選択されているパッケージで同じ名前のものがあればまとめる
  3264.  */
  3265. App.moveSameNamePackagesToPackage = function(event_name, selection)
  3266. {
  3267.     var duptable = {};
  3268.     var hitnames = {};
  3269.     selection.packages.forEach(function(x)
  3270.     {
  3271.         var n = x.getName();
  3272.         if (n in duptable)
  3273.         {
  3274.             duptable[n].push(x.UUID);
  3275.             hitnames[n] = duptable[n];
  3276.         }
  3277.         else
  3278.             duptable[n] = [x.UUID];
  3279.     });
  3280.    
  3281.     for (var x in hitnames)
  3282.     {
  3283.         var link_uuids = [];
  3284.         const p_uuids = hitnames[x];
  3285.         for (var i=1; i < p_uuids.length; i++)
  3286.         {
  3287.             const p = (this.CTX.getPackageByUUID)(p_uuids[i]);
  3288.             if (!p) continue;
  3289.            
  3290.             Array.prototype.push.apply(link_uuids, p.downloadLinks.map(xUUID));
  3291.         }
  3292.        
  3293.         if (link_uuids.length!==0)
  3294.             this.CTX.moveLinks(link_uuids, null, p_uuids[0]);
  3295.     }
  3296. };
  3297.  
  3298.  
  3299. /**
  3300.  *
  3301.  * この名前を使用して新しいパッケージに移動
  3302.  *
  3303.  * 【右クリックされた項目】
  3304.  *   名前/ダウンロードフォルダを新しいパッケージ名に使用
  3305.  *   取得できなければ何もしない
  3306.  *
  3307.  *   ※既存パッケージ名が使用された場合は、既存パッケージへの移動に切替
  3308.  *
  3309.  * 【選択されている項目】
  3310.  *   選択されているリンクやパッケージを新しいパッケージに移動
  3311.  *   パッケージのみが選択されている項目でもパッケージ内のリンク全てが移動の対象
  3312.  *
  3313.  */
  3314. App.moveToNewPackageWithFileName = function(event_name, selection)
  3315. {
  3316.     const IS_EXPAND_AFTER_MOVED = !!this.config.EXPAND_AFTER_NEWPACKAGE;
  3317.     const EXPAND_WAITTIME = this.config.WAITTIME_FOR_EXPAND_PACKAGE;
  3318.    
  3319.     var new_name = '';
  3320.     var new_p = null;
  3321.     var dlpath = null;
  3322.     var context_package_UUID = -1;          // 新規パッケージの位置用
  3323.     var prev_context_package_UUID = -1;     // 新規パッケージの位置用予備
  3324.    
  3325.     if (selection.isLinkContext())
  3326.     {
  3327.         new_name = getFileSpec(selection.contextLink.getName());
  3328.         dlpath = selection.contextLink.package.downloadFolder;
  3329.         context_package_UUID = selection.contextLink.package.UUID;
  3330.     }
  3331.     else if (selection.isPackageContext())
  3332.     {
  3333.         new_name = new_p.getName();
  3334.         dlpath = new_p.downloadFolder;
  3335.         context_package_UUID = new_p.UUID;
  3336.        
  3337.         new_p = selection.contextPackage;
  3338.     }
  3339.     if (new_name == '') return;
  3340.  
  3341.     // パッケージ新規作成後の移動先の予備を取得
  3342.     var all_p = this.CTX.getAllPackages();
  3343.     var prev = 0;
  3344.     if (all_p.some(function(p)
  3345.     {
  3346.         if (p.UUID == context_package_UUID)
  3347.             return true;
  3348.         prev = p.UUID;
  3349.     }))
  3350.     {
  3351.         prev_context_package_UUID = prev;
  3352.     }
  3353.    
  3354.     // パッケージが右クリックされていたら、そのパッケージnew_pを使い
  3355.     // リンクが右クリックされていたら、既存パッケージ名ならそのパッケージを使う
  3356.     if (! new_p)
  3357.         new_p = all_p.find(function(p){return p.getName()==new_name});
  3358.  
  3359.     var items = selection.links.map(xUUID);
  3360.    
  3361.     if (new_p)
  3362.     {   // 既存パッケージにリンクを移動
  3363.         this.CTX.moveLinks(items, null, new_p.UUID);
  3364.         if (context_package_UUID != -1)
  3365.         {
  3366.             sleep(10);
  3367.             this.CTX.movePackages([new_p.UUID], context_package_UUID);
  3368.         }
  3369.     }
  3370.     else
  3371.     {   // 新規パッケージにリンクを移動
  3372.         this.CTX.movetoNewPackage(items, null, new_name, dlpath);
  3373.         sleep(10);
  3374.        
  3375.         var l = (this.CTX.getLinkByUUID)(items[0]);
  3376.         this.CTX.movePackages(
  3377.             [l.package.UUID],
  3378.             this.CTX.getPackageByUUID(context_package_UUID) // 右クリックされたアイテムのパッケージがリンクの移動に伴って消滅しているか判定
  3379.                 ? context_package_UUID
  3380.                 : prev_context_package_UUID
  3381.         );
  3382.     }
  3383.    
  3384.     if (IS_EXPAND_AFTER_MOVED)
  3385.     {
  3386.         sleep(EXPAND_WAITTIME);
  3387.         l.package.setExpanded(true);
  3388.     }
  3389. };
  3390.  
  3391.  
  3392. ///// ソート関連 /////
  3393.  
  3394. function getAuthorsString(s)
  3395. {
  3396.     const exp_getAuthor = /^(?:\([^\)]+\) *)*\[([^\]]+)\] .+$/;
  3397.     const r = s.match(exp_getAuthor);
  3398.     if (!r) return '';
  3399. //  return r[1].split(/[×&、,,]/).map(function(a){return a.trim()}).filter(function(a){return !!a}).sort().join('×');
  3400. //  return r[1].split(/[×&、,,]/, 2).map(function(a){return a.trim()}).filter(function(a){return !!a}).sort().join('×');
  3401.     return r[1].split(/[×&、,,]/, 2).map(function(a){return a.trim()}).filter(function(a){return !!a}).join('×');
  3402. }
  3403.  
  3404. function getCategoryString(s)
  3405. {
  3406.     const exp_getCategory = /^(\([^\)]+\))/;
  3407.     const r = s.match(exp_getCategory);
  3408.     return r?r[1]:'';
  3409. }
  3410.  
  3411. /**
  3412.  * パッケージを並べ替える(画面上も)
  3413.  *
  3414.  * @param {(object,object)=>number} condition ソート関数に渡すコールバック関数
  3415.  */
  3416. App.sortPackages = function(condition)
  3417. {
  3418.     var packs = this.getSelection().packages;
  3419.     if (packs.length <= 1) packs = this.CTX.getAllPackages();
  3420.     if (packs.length <= 1) return;
  3421.    
  3422.     packs.sort(condition.bind(this));
  3423.    
  3424.     for (var i=1; i<packs.length;++i)
  3425.         this.CTX.movePackages([packs[i].UUID], packs[i-1].UUID);
  3426. };
  3427.  
  3428. /**
  3429.  * 作者順に並べ替え
  3430.  *
  3431.  * sortPackagesByAuthor*
  3432.  */
  3433. function sortfunc_CompareAuthorA(a,b)
  3434. {
  3435.     const author_a = getAuthorsString(a.name);
  3436.     const author_b = getAuthorsString(b.name);
  3437.     return author_a.localeCompare(author_b, LANGUAGE_JA);   // 原則日本語ファイル名に対応
  3438. }
  3439. function sortfunc_CompareAuthorB(a,b){return sortfunc_CompareAuthorA(b,a)}
  3440. App.sortPackagesByAuthorAscending  = function(){this.sortPackages(sortfunc_CompareAuthorA)};
  3441. App.sortPackagesByAuthorDescending = function(){this.sortPackages(sortfunc_CompareAuthorB)};
  3442.  
  3443. /**
  3444.  * タイトル順に並べ替え
  3445.  *
  3446.  * sortPackagesByTitle
  3447.  */
  3448. function sortfunc_CompareTitleA(a,b)
  3449. {
  3450.     /** @type {RegExp} 先頭から続く(~~)[~~]を削除するパターン */
  3451.     const exp_getTitle = /^(?:\([^\)]+\) *|\[[^\]]+\] *)*/;
  3452.     const title_a = a.name.replace(exp_getTitle, '');
  3453.     const title_b = b.name.replace(exp_getTitle, '');
  3454.    
  3455.     return title_a.localeCompare(title_b, LANGUAGE_JA); // 原則日本語ファイル名に対応
  3456. }
  3457. function sortfunc_CompareTitleB(a,b){return sortfunc_CompareTitleA(b,a)}
  3458. App.sortPackagesByTitleAscending  = function(){this.sortPackages(sortfunc_CompareTitleA)};
  3459. App.sortPackagesByTitleDescending = function(){this.sortPackages(sortfunc_CompareTitleB)};
  3460.  
  3461. /**
  3462.  * カテゴリ別作者順に並べ替え
  3463.  *
  3464.  * sortPackagesByAuthor*
  3465.  */
  3466. function sortfunc_CompareCategoryAuthorA(a,b)
  3467. {
  3468.     const categoryauthor_a = getCategoryString(a.name)+getAuthorsString(a.name);
  3469.     const categoryauthor_b = getCategoryString(b.name)+getAuthorsString(b.name);
  3470.     return categoryauthor_a.localeCompare(categoryauthor_b, LANGUAGE_JA);   // 原則日本語ファイル名に対応
  3471. }
  3472. function sortfunc_CompareCategoryAuthorB(a,b){return sortfunc_CompareCategoryAuthorA(b,a)}
  3473. App.sortPackagesByCategoryAuthorAscending  = function(){this.sortPackages(sortfunc_CompareCategoryAuthorA)};
  3474. App.sortPackagesByCategoryAuthorDescending = function(){this.sortPackages(sortfunc_CompareCategoryAuthorB)};
  3475.  
  3476. /**
  3477.  * カテゴリ別タイトル順に並べ替え
  3478.  *
  3479.  * sortPackagesByCategoryTitle
  3480.  */
  3481. function sortfunc_CompareCategoryTitleA(a,b)
  3482. {
  3483.     /** @type {RegExp} 先頭から続く(~~)[~~]を削除するパターン */
  3484.     const exp_getTitle = /^(?:\([^\)]+\) *|\[[^\]]+\] *)*/;
  3485.     const categorytitle_a = getCategoryString(a.name)+a.name.replace(exp_getTitle, '');
  3486.     const categorytitle_b = getCategoryString(b.name)+b.name.replace(exp_getTitle, '');
  3487.    
  3488.     return categorytitle_a.localeCompare(categorytitle_b, LANGUAGE_JA); // 原則日本語ファイル名に対応
  3489. }
  3490. function sortfunc_CompareCategoryTitleB(a,b){return sortfunc_CompareCategoryTitleA(b,a)}
  3491. App.sortPackagesByCategoryTitleAscending  = function(){this.sortPackages(sortfunc_CompareCategoryTitleA)};
  3492. App.sortPackagesByCategoryTitleDescending = function(){this.sortPackages(sortfunc_CompareCategoryTitleB)};
  3493.  
  3494.  
  3495. /**
  3496.  * 追加日時順に並べ替え
  3497.  *
  3498.  * sortPackagesByAddedDate
  3499.  */
  3500. function sortfunc_CompareAddedDateA(a,b){return a.getAddedDate()-b.getAddedDate()}
  3501. function sortfunc_CompareAddedDateB(b,a){return a.getAddedDate()-b.getAddedDate()}
  3502. App.sortPackagesByAddedDateAscending  = function(){this.sortPackages(sortfunc_CompareAddedDateA)};
  3503. App.sortPackagesByAddedDateDescending = function(){this.sortPackages(sortfunc_CompareAddedDateB)};
  3504.  
  3505. /**
  3506.  * 優先ホスト順に並べ替え
  3507.  *
  3508.  * sortPackagesByMyRule
  3509.  */
  3510.  
  3511. function getPointByRule(l,pats)
  3512. {
  3513.     var idx = pats.length;
  3514.     pats.some(function(m, i){if (m.test(getxUrl(l))){idx=i;return true}});
  3515.     return idx;
  3516. }
  3517. function sortfunc_CompareMyRuleA(a,b)
  3518. {
  3519.     const point_a = getPointByRule(a, this.config.priorityHostRule);
  3520.     const point_b = getPointByRule(b, this.config.priorityHostRule);
  3521.     return (point_a == point_b)
  3522.         ? sortfunc_CompareCategoryTitleA(a,b)
  3523.         : (point_a - point_b);
  3524. }
  3525. function sortfunc_CompareMyRuleB(a,b)
  3526. {
  3527.     const point_a = getPointByRule(a, this.config.priorityHostRule);
  3528.     const point_b = getPointByRule(b, this.config.priorityHostRule);
  3529.     return (point_a == point_b)
  3530.         ? sortfunc_CompareCategoryTitleA(b,a)
  3531.         : (point_b - point_a);
  3532. }
  3533. App.sortPackagesByMyRuleAscending=function()
  3534. {
  3535.     this.sortPackages(sortfunc_CompareMyRuleA)
  3536. };
  3537. App.sortPackagesByMyRuleDescending=function()
  3538. {
  3539.     this.sortPackages(sortfunc_CompareMyRuleB);
  3540. };
  3541.  
  3542. ///// リンクグラバーへの追加関連 /////
  3543.  
  3544. /**
  3545.  * リンクからリンクへ情報をコピーする
  3546.  *
  3547.  * @param {number} xUUID 情報コピー元リンクのUUID(コンテキストに応じたDownloadLinkかCrawledLink)
  3548.  * @param {number} cUUID 情報コピー先リンクのUUID(CrawledLinkのみ)
  3549.  * @returns {boolean} true:成功、false:失敗
  3550.  */
  3551. App.copyXLinkToCLink = function(xUUID, cUUID)
  3552. {
  3553.     var xl = this.CTX.getLinkByUUID(xUUID);
  3554.     var cl = this.LGC.getLinkByUUID(cUUID);
  3555. //alert({xl:xl,cl:cl});
  3556.     if (!xl || !cl) return false;
  3557.  
  3558.     // Copy Link properties
  3559.     // link.getProperties() returns "Map" Embed Java Object.
  3560.     // props.keySet() returns "Set" Embed Java Object.
  3561.     // props.keySet().toArray() returns object is abled to use as Array
  3562.     // props.get(key) returns a value of key
  3563.     var props = xl.getProperties();
  3564.     if (props)
  3565.     {
  3566.         props.keySet().toArray().forEach(function(k){
  3567.             // without JOB_ID, old jobId is not available.
  3568.             if (k == 'JOB_ID') return;
  3569.             var v = props.get(k);
  3570.            
  3571.                 // link.setProperty() does not support Object
  3572.             if (typeof v === 'object')
  3573.                 return;
  3574.            
  3575.             cl.setProperty(k, v);
  3576.         });
  3577.     }
  3578.    
  3579.     // Copy link comment
  3580.     if (xl.comment)
  3581.     {
  3582.         cl.comment = xl.comment;
  3583.     }
  3584.    
  3585.     // Copy package comment
  3586.     if (xl.package.comment && !cl.package.comment)
  3587.     {
  3588.         cl.package.comment = xl.package.comment;
  3589.     }
  3590.    
  3591.     // Copy link priority
  3592.     cl.priority = xl.priority;
  3593.    
  3594.     // Copy link enabled state
  3595.     if (xl.isEnabled() != cl.isEnabled())
  3596.     {
  3597.         cl.setEnabled(xl.isEnabled());
  3598.     }
  3599.    
  3600.     // Copy link name
  3601.     cl.setName(xl.getName());
  3602.     return true;
  3603. };
  3604.  
  3605. /**
  3606.  * リンク追加時のオンラインチェックの有効/無効設定
  3607.  *
  3608.  * @param {boolean} enabled true:有効、false:無効
  3609.  * @returns {boolean} 前の設定状態を返す
  3610.  */
  3611. function setDoLinkCheck(enabled)
  3612. {
  3613.     var old = callAPI("config", "get", "jd.controlling.linkcollector.LinkCollectorConfig", null, "DoLinkCheck");
  3614.     if (old != enabled)
  3615.         callAPI("config", "set", "jd.controlling.linkcollector.LinkCollectorConfig", null, "DoLinkCheck", enabled);
  3616.     return old;
  3617. }
  3618.  
  3619. /**
  3620.  * 渡されたアイテムからURLの配列を返す
  3621.  * ( APIが返したCrawledLink | CrawledLinkオブジェクト | URL文字列 )のいずれかを受け取って
  3622.  * ダウンロード先を指すURLの配列を返す(登録時のURLと同一であるものがどれか分からないため候補を複数返す)
  3623.  */
  3624.  
  3625. App.getXItemUrls = function(item)
  3626. {
  3627.     var urls = [];
  3628.     var x = item;
  3629.     if (x.uuid !== undefined) // api link
  3630.     {
  3631.         if (x.url)
  3632.             urls.push(x.url);
  3633.         var tmp = this.LGC.getLinkByUUID(x.uuid)||this.DLC.getLinkByUUID(x.uuid);
  3634.         if (tmp)
  3635.             x = tmp;
  3636.     }
  3637.     if (x.UUID) // JD Link Object
  3638.     {
  3639.         ['pluginURL','contentURL','originURL'].forEach(function(p){
  3640.             if (x[p]) urls.push(x[p]);
  3641.         });
  3642.         ['URL_CONTENT','ORIGIN_URL'].forEach(function(p){
  3643.             var t = x.getProperty(p);
  3644.             if (t) urls.push(t);
  3645.         });
  3646.     }
  3647.     else if (isString(x))
  3648.         urls.push(x);
  3649.    
  3650.     return urls.map(function(u){return u.replace(/#.*$/,'')}).filter(xDup);
  3651. //  return urls;
  3652. };
  3653.  
  3654. /**
  3655.  * crawlerJobの中止
  3656.  * @param {number[]} jobIds JOB IDの配列
  3657.  */
  3658. App.abortJob = function(jobIds)
  3659. {
  3660.     jobIds.forEach(function(j){this.LGC.abort(j)},this);
  3661. };
  3662.  
  3663. /**
  3664.  * リンクグラバーへ再登録
  3665.  *
  3666.  * 選択されているリンクを全てリンクグラバーに追加
  3667.  * (パッケージ選択時はパッケージ内全リンク)
  3668.  *
  3669.  * ※ リンクグラバーの登録処理時間が異常に長過ぎた場合、
  3670.  *    登録後のファイル名の設定をせずにスクリプトを終了
  3671.  */
  3672. App.addLinksToLinkGrabber = function(event_name, selection)
  3673. {
  3674.     //
  3675.     // 「URL→選択されたリンクのテーブルを用意」
  3676.     // 「重複URLは排除」
  3677.     //
  3678.     const sellinks = selection.links;
  3679.     if (0 == sellinks.length) return;
  3680.     const selpacks = selection.packages;
  3681.     const check_interval = 500;
  3682.     const limit_time = (sellinks.length * 2000 + 10000) + getCurrentTimeStamp();
  3683.    
  3684.     var jobid_list = Object.create(null);
  3685.     var url_map = Object.create(null);// url to downloadLink's UUID map
  3686.     var dupUrlIdx_set = Object.create(null);// duplicated url index set
  3687.     var packed_sellinks = Object.create(null);
  3688.     var cl_to_xl = Object.create(null);
  3689.     var is_aborted = false;
  3690.    
  3691.     // URLの重複チェック+URLtoDL.UUIDの変換テーブル作成
  3692.     sellinks.forEach(function(x,i){
  3693.         if (url_map[getxUrl2(x)])
  3694.         {
  3695.             dupUrlIdx_set[i] = 1;
  3696.             return;
  3697.         }
  3698.        
  3699.         url_map[getxUrl2(x)]={UUID:x.UUID, packageUUID:x.package.UUID};
  3700.         packed_sellinks[x.package.UUID]
  3701.             ? packed_sellinks[x.package.UUID].push(x)
  3702.             : (packed_sellinks[x.package.UUID]=[x]);
  3703.     });
  3704.    
  3705.     // 確認 URLの重複無視で処理続行
  3706.     if (keys(dupUrlIdx_set).length !== 0)
  3707.     {
  3708.         if (this.confirm('ADDLINKSTOLINKGRABBER_IGNORE_DUP_URL','CONTINUE','ABORT'))
  3709.             return false;c
  3710.     }
  3711.    
  3712.     // 一時的にオンラインチェックを止める
  3713.     //Settings > Advanced Settings > LinkCollector.dolinkcheck > Disable
  3714.     var old_islinkcheck = setDoLinkCheck(false);
  3715.    
  3716.     try {
  3717.         //
  3718.         // パッケージ単位で一気にaddLinks
  3719.         //
  3720.         selpacks.forEach(function(xp,i)
  3721.         {
  3722.             // パッケージxp内のURL重複分を省いた『選択されているリンクのリスト』
  3723.             var xp_links = packed_sellinks[xp.UUID];
  3724.             if (xp_links.length === 0) return;
  3725.            
  3726.             var jobid = this.LGC.addLinks({
  3727.                 links: xp_links.map(getxUrl).join('\r\n'),
  3728.                 packageName: xp.getName(),
  3729.                 overwritePackagizerRules: true,
  3730.                 destinationFolder: xp.downloadFolder,
  3731.                 deepDecrypt: false,
  3732.                 assignJobID: true,  // assignJobID FAQ = https://support.jdownloader.org/en/knowledgebase/article/use-jobids-to-keep-link-references
  3733.             });
  3734.             if (!jobid) return;
  3735.             jobid_list[jobid.id] = xp_links.map(getxUrl);
  3736.         }, this);
  3737.         if (0 === keys(jobid_list).length) return;
  3738.  
  3739.         // require wait time
  3740.         sleep(1000);
  3741.  
  3742.         var all_jobs = null, addedlinks_api = null;
  3743.         // jobの完了待ちループ
  3744.         while (1)
  3745.         {
  3746.             // Timeoutエラー処理
  3747.             // Crawling中なら中断しない
  3748.             if ((!callAPI("linkcrawler","isCrawling")&&!callAPI("linkgrabberv2","isCollecting"))
  3749.                 && getCurrentTimeStamp() > limit_time)
  3750.             {
  3751.                 alert('Error: Timeout App.addLinksToLinkGrabber()');
  3752.                 this.abortJob(keys(jobid_list));
  3753.                 is_aborted = true;
  3754.                 break;
  3755.             }
  3756.             //
  3757.             // 全jobをqueryLinkCrawlerJobs
  3758.             //
  3759.             all_jobs = this.LGC.queryLinkCrawlerJobs({
  3760.                 collectorInfo:true,
  3761.                 jobIds:keys(jobid_list)
  3762.             });
  3763.             if (!all_jobs)
  3764.             {   // nullを返さないので通常はここに来ない
  3765.                 alert('Error: queryLinkCrawlerJobs() returns null', 'Aborted');
  3766.                 this.abortJob(keys(jobid_list));
  3767.                 is_aborted = true;
  3768.                 break;
  3769.             }
  3770.            
  3771.             //
  3772.             // 全jobの内、完了したjobからcrawledカウンターが0でないjobのみqueryLinks
  3773.             //
  3774.             var finished_jobs = all_jobs.filter(function(j){return !j.checking && !j.crawling
  3775.                 && (j.crawled||j.filtered||j.broken||j.unhandled)});
  3776.             if (finished_jobs.length)
  3777.             {
  3778.                 var crawled_jobIds = finished_jobs.reduce(function(a,j){if (j.crawled||j.filtered) a.push(j.jobId);return a},[]);
  3779. //              var crawled_jobIds = finished_jobs.filter(function(j){return j.crawled||j.filtered});
  3780.                 if (crawled_jobIds.length)
  3781.                 {
  3782.                     //
  3783.                     // 完了したjobからcrawledカウンターが0でないjobのみqueryLinks
  3784.                     //
  3785.                     addedlinks_api = this.LGC.queryLinks({
  3786.                         jobUUIDs: crawled_jobIds,
  3787.                         url:true,
  3788.                         uuid:true,
  3789.                     });
  3790.                     if (!addedlinks_api)
  3791.                     {
  3792.                         alert('Error: queryLinks() returns null', 'Aborted');
  3793.                         this.abortJob(keys(jobid_list));
  3794.                         is_aborted = true;
  3795.                         break;
  3796.                     }
  3797.                     addedlinks_api.forEach(function(cl_api)
  3798.                     {
  3799.                         // リンクのURLがurl_mapに無いならスキップ
  3800.                         var url = this.getXItemUrls(cl_api).find(function(u){return url_map[u]});
  3801.                         if (!url) return;
  3802.                        
  3803.                         cl_to_xl[cl_api.uuid] = url_map[url];
  3804. //                      this.copyXLinkToCLink(xl_uuid, cl_api.uuid);
  3805.                        
  3806.                         // 完了したリンクのURLをurl_mapから削除
  3807.                         delete url_map[url];
  3808.                     },this);
  3809.                    
  3810. //                  alert({finished_jobIds:finished_jobs.map(function(j){return j.jobId}), crawled_jobIds:crawled_jobIds, finished_jobs:finished_jobs});
  3811.                 }
  3812.                 else
  3813.                 {
  3814.                     // crawledカウンターが0、有効なリンク無し(重複リンクもここに来る)
  3815.                 }
  3816.                
  3817.                 // 完了したjobIdを削除
  3818.                 finished_jobs.forEach(function(j){delete jobid_list[j.jobId]});
  3819.                
  3820.                 // 待機中のjobIdが無ければjob完了待ちループを抜ける
  3821.                 if (0===keys(jobid_list).length)
  3822.                 {
  3823.                     break;
  3824.                 }
  3825.                 if (is_aborted) break;
  3826.             }//if (crawled_jobIds.length)
  3827.             sleep(check_interval);
  3828.         }// while (1)
  3829.     }
  3830.     finally
  3831.     {
  3832.         // オンラインチェック設定復帰
  3833.         if (old_islinkcheck)
  3834.             setDoLinkCheck(old_islinkcheck);
  3835.     }
  3836.    
  3837.     if (keys(cl_to_xl).length !== 0)
  3838.     {
  3839.         // 登録元パッケージ順にソート
  3840.         var orders = {};
  3841.         selpacks.forEach(function(p,i){orders[p.UUID]=i});
  3842.         var cp_uuids = keys(cl_to_xl).map(function(cl){return this.LGC.getLinkByUUID(cl)},this)
  3843.             .sort(function(a,b){return orders[cl_to_xl[a.UUID].packageUUID] - orders[cl_to_xl[b.UUID].packageUUID]})
  3844.             .map(function(x){return x.package.UUID})
  3845.             .filter(xDup);
  3846.         for (var i=1; i<cp_uuids.length;++i)
  3847.             this.LGC.movePackages([cp_uuids[i]], cp_uuids[i-1]);
  3848.        
  3849.         // 新規リンクへ情報をコピー
  3850.         keys(cl_to_xl).forEach(function(cl_uuid){
  3851.             this.copyXLinkToCLink(cl_to_xl[cl_uuid].UUID, cl_uuid);
  3852.         },this);
  3853.        
  3854.         // オンラインチェック
  3855. //      this.LGC.startOnlineStatusCheck(null, cp_uuids);
  3856.         this.LGC.startOnlineStatusCheck(keys(cl_to_xl), null);
  3857.     }
  3858.    
  3859.     return !is_aborted;
  3860. };
  3861.  
  3862. App.openJDCfgFolder = function(event_name, selection)
  3863. {
  3864.     callSync( this.config.path.filer,  DQ(jdPath('cfg')) );
  3865. };
  3866.  
  3867.  
  3868. disablePermissionChecks();
  3869.  
  3870. ///// init
  3871. App.init();
  3872. try
  3873. {
  3874.     App.setEventProperty({
  3875.         menu:menu,
  3876.         name:name,
  3877.         icon:icon,
  3878.         shortCutString:shortCutString,
  3879.     });
  3880. }
  3881. catch(e)
  3882. {
  3883.     throw Error(App.getResource('ERROR_INVALID_TRIGGER_TYPE'));
  3884. }
  3885. //alert("App.resource.getLanguages()="+App.resource.getLanguages()+"\n\n"+"App.dispatcher.getNames()=\n"+App.dispatcher.getNames().join('\n')+"\n");
  3886.  
  3887. App.dispatch();
  3888.  
  3889.  
  3890. ///// [exit script]
  3891.  
  3892.  
  3893.  
Comments
  • usamimi2323
    2 days
    # text 0.26 KB | 0 0
    1. 『WEB検索』するにはUserConfig.SEARCH_WEBを自分仕様に書き換える
    2.  
    3. 『名前を元に戻す』を使用するには別途EventScriptが必要※1
    4. ※1 「名前を元に戻す」用リンクプロパティ設定 ( https://pastebin.com/T8DsXmbz )
Add Comment
Please, Sign In to add comment