プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

ぷち系DL回収作業メモとスクリプト

回収作業に当たり、やって頂きたい事

【注】12/26 時点 : ぷちデレラに関する回収タスクは現在ありません

「スクリプト(コード)の実行ってどうやるの?」という方は、
まず最初に リンク先記事の [Tips-2] スクリプト実行 解説部分をお読みください

https://tech.packetroom.net/imcg-idol-data-dl-complement

※ DLファイル数が多い場合、ブラウザから複数DLの許可を求められるケースがあるので、都度応じてください。

  1. [Task-1] 各エピソードリストDLを一括実行 & 開放済ぷち確認

    まず 本記事ページ中ほどに掲載されている [A-1] 部分のコードをコピーし、
    それを ぷちデレラ系ページなら割とどこでも良いので DevTools の Console 上で実行。

    そうすると 全開放されているぷちデレラのみ petit_episode_list_{アイドル名}.json がDLされる。
    最後に、その結果をまとめた .txt ログも出力される。
    この [Task-1] 作業は1回で完了

  2. [Task-2] (開放済みの)ぷちデレラ別でDL回収

    上記 [Task-1] ( =[A-1]実行 ) により 〇〇.json へ書き出された各ぷちデレラ
    ( つまり、 結果ログ .txt「開放済み 〇」 の記載があるぷちデレラ )
    ...が、LvMAX + テクニカルボード全開放 を満たしている 適合ぷちデレラ となります。
    ※ 適合していても、既に回収表が埋まっている場合は対象外です。
    ※ 確認に必要な 「エピソード系回収リスト」 は、本記事の中ほどをご覧ください。

    回収手順は以下。

    まず、[B-1] で提示されているコードを実行。
    すると {ぷちデレラID}_{エピソード数}.flashext が 8つ および
    {ぷちデレラID}_petit_serif.json ファイルが 1つ DLされるはず。

    [Task-2] におけるDL作業は、回収対象のぷちプロフページごとに都度実行してください。

    ※ 後述の理由から、まず対象ぷちデレラ分の .flashext を一気にDLしておく事を推奨

  3. [Task-3] フォルダ分け & リネーム作業

    上記[Task-1]と[Task-2]の順次実行をすべて終えると、
    ぷちデレラ1体分につき、以下のファイルが出そろうはずです。

    • {ぷちデレラID}_{エピソード数}.flashext ( .swf ) x 8個
    • {ぷちID}_petit_serif.json x 1個
    • petit_episode_list_{アイドル名}.json x 1個

    お手間でなければ、以下ルールに則った
    リネーム & ぷちデレラ別フォルダ分け 作業 もお願いします。

    • [Task-3.1] 拡張子変更作業
      .flashext の拡張子を全て .swf へ変更してください。
      ※ 下記手順を踏むと一瞬で終わります

      (1) DLした全ての .flashext を、適当なフォルダへ一旦ごっそり移動。
      (2) そのフォルダ内で適当なテキストファイル .txt を作成 + 下記内容コピペで保存。
      ▼ ファイル内容はこの1行だけ

      ren *.flashext *.swf

      ※「この命令文みたいなの何?」と不安な方は、一度ググって頂ければと。

      (3) そのまま拡張子を .txt から .bat へ変更し、その場でダブルクリック実行。
      こうすると、その階層.flashext 拡張子が全て .swf に変更されます。
      ( ※ フォルダ分け前にこの作業をすれば、一度の実行で済みます )

      • ※ 拡張子を .swf でDLしない理由
        元から .swf でもDL出来るのですが、その場合ブラウザから危険なファイルとして扱われ、
        都度 DL許可作業 を挟まなければいけない為、手間削減の観点から偽装DLしています。
        ( 拡張子変更なら、上記.batの利用ですぐ終わる...というのも理由です )
    • [Task-3.2] フォルダ分け と配置

      ぷちデレラID別のフォルダ(1xxxのような半角の4桁数字となる)を作り、そこへ対象ぷちデレラの
      {ぷちID}_{エピ数}.flashext ( もとい.swf )ファイル 8つ のみ を配置

      petit_episode_list_{アイドル名}.json および
      {ぷちID}_petit_serif.json の2種は ぷちデレラ別のフォルダに混入させず
      「エピソードリスト用」と「セリフ用」のフォルダを2つだけ作成し、
      そこへ全アイドル分をまとめて一括格納して頂けると助かります。

  4. [Task-4] チャンネル内にぶん投げる
    なんか適当に .zip とかで圧縮して、チャンネルにご報告兼ねて投げて頂ければと。
    Discordの仕様上、あんまり大きなファイルをドンと置けないらしく、
    複数ぷちデレラにわたって一気に作業回収された場合、4体ほど?に小分けにしておくと躓かずuploadできそうです。

ぷちエピソードリスト(一括/個別)取得

ぷちエピソードの開放確認用APIのリザルトを回収DL保存

[A-1] 全員一括DL + 確認版

  • 実行場ページ
    ぷちデレラ系ページなら多分どこでも?
    実行中のページ上 ( ※ DeVtool上ではない ) で Esc キーを押すと、以降のDLが中断破棄
  • 概要 / 動作内容
    このスクリプトを実行すると「ぷちデレラのぷちエピソード開放具合」を問い合わせるAPIに対し、
    実装済ぷちデレラ全員( 卯月~颯 までの193人分 )のアクセスおよびそのDL回収を試みます。
    現行DLフォーマットは petit_episode_list_{アイドル名}.json です。
    エピ解放済みぷちデレラのみ DLされ、それ以外はスキップされます。
    ( ※ エピソード全開放がDL条件です )
  • 補足用のtxtファイル
    行程終了後、以下の結果まとめが .txt でDLされます。 ( 通知ボイス再生 + Console にも表示 )

    ぷちID 属性 アイドル名 DLまたは開放状況
    4桁の数字 属性 未加入だと未記載 ... 少なくともエピはコンプ済みなのでDL完了 ( .json が得られている筈 )
    ... ぷちは所属しているが、エピソード未コンプ
    × ... そもそもぷちデレラ未加入

余談ですが、ぷちセリフ(コメント系)は Lv30+ボード全開放が条件となっています。
このセクションではエピソードリストのDLなので、テクニカルボード上のエピを全開放していればいけるハズ
( そもそもの内部処理で、全開放=8種類 無ければDLしないようになっています )

[Script : A-1] ぷちエピソードリストDL取得 / 対象一括DL版


(async function(){

    const _dlTargetDefObj = [];
    let _appendCount = 192; // 最初の一人 + 192 = 193人分 (卯月~颯)
    window.isFileDlStop = false;
    for(let _n=0; _appendCount>_n; _n++){
        const _targetVal = (1001 + _n); // 基点番号
        const _url = `https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fpetit_cg%2Fajax_episode_list`;
        // ファイル名基本フォーマット (拡張子除く)
        const _saveFileFormatName = `petit_episode_list_${_targetVal}`;
        const _postObj = { "idol_id" : _targetVal };
        _dlTargetDefObj.push( { "filename" : _saveFileFormatName , "url" : _url , "PostObject" : _postObj , "petit_id" : _targetVal } );
    }
    const _resultMap = new Map();
    console.info("=================================================");
    console.info("DL処理を開始します。 中止したい場合はページ上で Esc(エスケープキー) を押してください");
    console.info("=================================================");
    console.info(`DL予定数合計 :  ${_appendCount}`);
    const _arrtDefArr = ["*","Cute","Cool","Passion"]; // 属性定義
    function _fileDl(_dlInfo , _index) {
        return new Promise((resolve,reject) => {
            const _fileName = _dlInfo.filename;
            const _url = _dlInfo.url;
            if(window.isFileDlStop){
                console.info("以降のDLを全て中止します");
                reject();
                return;
            }
            setTimeout(() => {
                const _req = new XMLHttpRequest();
                _req.open("POST", _url , true);
                _req.responseType = "json";
                _req.onload = function(oEvent) {
                    const _data = _req.response;
                    try{
                        const _setPetitId = _dlInfo["petit_id"];
                        const _setAttr = _data?.["idol_attribute"] || 0;
                        let _setNname = "";
                        let _setDlCheck = "";
                        if(_data?.episode_list?.length >= 8){ // エピソード数は8個
                            const _name = _data.episode_list[0]["episode_name"].match(/([\W]*)/)[1];
                            const _blob = new Blob([JSON.stringify(_data,null,"\t")] , {type: "application\/json"});
                            const _dlUrl = URL.createObjectURL(_blob);
                            const _a = document.createElement('a');
                            _a.href = _dlUrl;
                            _a.download = `${_fileName}_${_name}.json`;
                            document.body.appendChild(_a);
                            _a.click();
                            document.body.removeChild(_a);
                            URL.revokeObjectURL(_dlUrl);
                            _setDlCheck = "〇";
                            _setNname = _name;
                            console.info(`${_setDlCheck} ${_a.download} : ${_blob.size} byte ${_index+1} 個目のDLが完了しました`);
                        }else{
                            if(_setAttr == 0){
                                _setDlCheck = "×";
                                console.log(_setDlCheck , _setPetitId , "は ぷちデレラ未加入と判断されました");
                            }else{
                                _setDlCheck = "△";
                                console.log(_setDlCheck , _setPetitId , "は Lvまたはボード等が未コンプ状態と判断されました");
                            }
                        }
                        // ぷちIDをキーとして [ 属性, (得られれば)名前 , (DL出来たなら)DL成功の旨 ] の配列をMAP格納
                        _resultMap.set( _setPetitId , [ _arrtDefArr[_setAttr] , _setNname , _setDlCheck]);
                    }catch(_e){
                        console.warn(_e);
                    }
                    resolve();
                };
                const _formData = new FormData();
                // POST あり
                if(_dlInfo["PostObject"]){
                    const _pObjs = _dlInfo["PostObject"];
                    for(const _keyName in _pObjs){
                        const _val = _pObjs[_keyName];
                        _formData.append( _keyName , _val );
                    }
                    _req.send( _formData ); 
                }
                else{
                    _req.send();    
                }
            }, 100);    // DL間隔ミリ秒。あまり縮めないほうが良い
        });
    }

    // 実処理開始
    (async () => {
        await _dlTargetDefObj.reduce((promise, _dlInfo , _currentIndex) => {
            return promise.then(async () => {
                await _fileDl(_dlInfo , _currentIndex);
            });
        }, Promise.resolve());
        console.info("=== 予約中のデータを全てDLしたと判断されました ====");
        {
            //console.table(_resultMap);
            let _outStr = "ぷちID / 属性(または未加入) / アイドル名 / DL(=エピソード一覧json取得)状況\n※ タブで整形されているので、Excel等にそのまま貼り付けられます\n\n";
            _resultMap.forEach((_e , _key)=>{
                _outStr += ( _key + "\t" + _e.join("\t") ) + "\n";
            });
            console.log(_outStr);
            const _dlUrl = URL.createObjectURL( new Blob([_outStr+"\n\n"] , {type: "text/plain"}) );
            const _a = document.createElement('a');
            _a.href = _dlUrl;
            _a.download = `_ぷちエピソードDL成功一覧.txt`;
            document.body.appendChild(_a);
            _a.click();
            document.body.removeChild(_a);
            URL.revokeObjectURL(_dlUrl);
        }
        // 全DL完了時処理
        {
            const _file = "https://resource.mobamas.net/idolmaster/sound/3207601/637e9bda8de8a884cf5ee0f9b695d5f8.mp4";
            const audioContext = new (window.AudioContext || window.webkitAudioContext)();  
            fetch(_file).then(_res=>{
                return _res.arrayBuffer();
            }).then(_arrayBuffer => {
                audioContext.decodeAudioData(_arrayBuffer , _audioBuffer =>{
                    const _source = audioContext.createBufferSource();
                    _source.buffer = _audioBuffer;
                    const _gainNode = audioContext.createGain();
                    _source.connect(_gainNode);
                    _gainNode.connect(audioContext.destination);
                    _gainNode.gain.value =  0.2;    // 音量
                    _source.start(0);
                });
            });
        }
    })();

    // DL中止用
    document.body.addEventListener("keydown" , e=>{
        if (e.code === "Escape") {
            window.isFileDlStop = true;
        }
    });

})();



[A-Sub] 個別DL版

  • 実行ページ
    個々のぷちプロフページ
    ※ 「ぷちめにゅー」 から 「リスト」 経由で ぷちプロフに移動 ( 着せ替え用ページ )
  • 概要 / 動作内容
    ほとんどの動作や仕様は、前項の [A-1] 全員一括版と変わりません。
    大きな違いとして、そのぷちデレラのみ対象として、エピソードリストの.jsonをDLします。
    ※ 未開放の場合は当然DLに失敗します。
[Script : A-sub] ぷちエピソードリストDL / 個別実行版 ( ※ [A-1] が正しく実行済みなら不要 )


(async function(){
    const _m = location.href.match(/%2Fpetit_cg%2Fcoordinate_idol%2F([\d]*)/);
    let _isDlFlag = false;
    if(!_m || !_m?.[1]){
        console.info(_m?.[1]);
        window.alert("個別のぷちプロフページで実行してください");
        return;
    }
    const _targetVal = _m[1];
    console.info(_targetVal);

    const _url = `https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fpetit_cg%2Fajax_episode_list`;
    const _saveFileFormatName = `petit_episode_list_${_targetVal}`;
    const _postObj = { "idol_id" : _targetVal };
    const _dlTargetDefObj = { "filename" : _saveFileFormatName , "url" : _url , "PostObject" : _postObj , "petit_id" : _targetVal };

    async function _fileDl(_dlInfo) {
        const _req = new XMLHttpRequest();
        _req.open("POST", _url , true);
        _req.responseType = "json";
        _req.onload = function(oEvent) {
            const _data = _req.response;
            try{
                const _setPetitId = _dlInfo["petit_id"];
                const _setAttr = _data?.["idol_attribute"] || 0;
                const _fileName = _dlInfo.filename;
                let _setNname = "";
                let _setDlCheck = "";
                if(_data?.episode_list?.length >= 8){ // エピソード数は8個
                    const _name = _data.episode_list[0]["episode_name"].match(/([\W]*)/)[1];
                    const _blob = new Blob([JSON.stringify(_data,null,"\t")] , {type: "application\/json"});
                    const _dlUrl = URL.createObjectURL(_blob);
                    const _a = document.createElement('a');
                    _a.href = _dlUrl;
                    _a.download = `${_fileName}_${_name}.json`;
                    document.body.appendChild(_a);
                    _a.click();
                    document.body.removeChild(_a);
                    URL.revokeObjectURL(_dlUrl);
                    _setDlCheck = "〇";
                    _setNname = _name;
                    console.info(`${_setDlCheck} ${_a.download} : ${_blob.size} byte のDLが完了しました`);
                }else{
                    if(_setAttr == 0){
                        _setDlCheck = "×";
                        console.log(_setDlCheck , _setPetitId , "は ぷちデレラ未加入と判断されました");
                    }else{
                        _setDlCheck = "△";
                        console.log(_setDlCheck , _setPetitId , "は Lvまたはボード等が未コンプ状態と判断されました");
                    }
                }
            }catch(_e){
                console.warn(_e);
            }
        };
        const _formData = new FormData();
        if(_dlInfo["PostObject"]){
            const _pObjs = _dlInfo["PostObject"];
            for(const _keyName in _pObjs){
                const _val = _pObjs[_keyName];
                _formData.append( _keyName , _val );
            }
            _req.send( _formData ); 
        }
    }

    await _fileDl(_dlTargetDefObj);

})();



[B-1] 単体のぷちエピソード一括&セリフ同時DL

  • 実行ページ
    個々のぷちプロフページ
    ※ 「ぷちめにゅー」 から 「リスト」 経由で ぷちプロフに移動 ( 着せ替え用ページ )
  • 概要 / 動作内容
    ぷちデレラのエピソード本体ファイル .swf ( .flashext ) をDL
    ぷちデレラのセリフ一覧の定義ファイル .json をDL
[Script : B-1] ぷち別エピソード.swf 一括DL および エピソードリスト.jsonのDL

(async()=>{
    const _petitId = (()=>{ // URLか内部定義からぷちIDを取得
        const _id = location.href.match(/%2Fpetit_cg%2Fcoordinate_idol%2F([\d]*)/)?.[1] || mc.character['1']?.idol_id;
        if(_id && _id > 1000){ return _id; }
    })();
    if(!_petitId){ return console.error("ぷちID取得に失敗"); }
    const _cmtData = comment_list;
    if(!_cmtData){ return console.error("LV30+ボード全開放条件未達成されていない様です。ぷちセリフなどDLできません。"); }
    if(_cmtData){   // セリフリスト即時をDL
        const _url = URL.createObjectURL( new Blob([JSON.stringify(_cmtData,null,'\t')],{type:'application\/json'}) );
        const _link = document.createElement("a");
        //const _name = mc.character['1'].short_name;
        _link.href = _url;
        _link.download = `${_petitId}_petit_serif.json`;
        document.body.appendChild(_link); _link.click(); document.body.removeChild(_link);
        URL.revokeObjectURL(_url);
    }
    const _dlTargetDefArr = [];
    {   
        // エピソードリスト取得
        const _epiurl = "https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fpetit_cg%2Fajax_episode_list";
        const _formData = new FormData();
        _formData.append('idol_id', _petitId);
        const _epData = await fetch(_epiurl,{ method: "POST",body: _formData }).then(_res=>{ return _res.json(); });
        console.info(_epData);
        const _ep_list = _epData?.episode_list;
        _ep_list.forEach(_ep=>{
            console.info(_ep);
            const _epNum = _ep?.episode_id;
            let _saveFileFormatName = `${_petitId}_${_epNum}`;
            // ボイス無しクエリURLでDL
            const _dlSwfUrl = `https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fpetit_cg`
            +`%2Fpetit_show_episode_swf%2F${_petitId}%2F${_epNum}%2Fcoordinate_idol%2F1%2F0%2F0`;
            _dlTargetDefArr.push( { "filename" : _saveFileFormatName , "url" : _dlSwfUrl , "optionExt" : ".flashext"} );
        });
    }
    console.info(`========= DL処理を開始 計 ${_dlTargetDefArr.length} =========`);
    // 実処理開始
    (async () => {
        await _dlTargetDefArr.reduce((promise, _dlInfo , _currentIndex) => {
            return promise.then(async () => {
                await _fileDl(_dlInfo , _currentIndex);
            });
        }, Promise.resolve());
        console.info("========= 予約中のデータを全てDLしたと判断されました =========");
    })();

    function _fileDl(_dlInfo , _index) {
        return new Promise((resolve,reject) => {
            setTimeout(() => {
                try{
                    const _fileName = _dlInfo["filename"];
                    const _fileUrl = _dlInfo["url"];
                    fetch(_fileUrl)
                    .then((_res)=> _res.blob())
                    .then((_blob)=>{
                        const _a = document.createElement("a");
                        const _dataUrl = URL.createObjectURL(_blob);
                        _a.href = _dataUrl;
                        // ファイル名+拡張子
                        _a.download = `${_fileName}${(_dlInfo?.optionExt?_dlInfo?.optionExt:"")}`;
                        document.body.appendChild(_a);  _a.click();     document.body.removeChild(_a);
                        console.info(_a.download , `size:${_blob.size}byte , ${_index+1} 個目のDLが完了しました`);
                        URL.revokeObjectURL(_dataUrl);
                    });
                }catch(_e){ console.warn(_e); }
                resolve();
            }, 100);    // DL間隔ミリ秒。あまり縮めないほうが良い
        });
    }
})();

検索ネタ

( ご存じの方/知ってるけど使ってない ってほうが圧倒的に多そうですが )

  1. ブラウザページ上で Ctrl+F を実行
  2. 右上に検索フォームが出るので、名前を入力
  3. 実行すると、即時そのアイドル名へジャンプするので調べやすい

エピソード系回収リスト

ご協力により、全ぷちデレラ分を回収できました

  • .swfDL ... [B-1] で個別に入手可能
  • エピリストDL ... [A-1] で一括入手可能 / 個別なら [A-Sub] がオススメ
  • セリフリストDL ... [B-1] で個別に入手可能
  • エピボイスDL ... エピの .swf さえ手元にあれば 作者側でDL可能なので手法未記載
    ( 分かりやすいように記事こさえるのも大変なのれす... )
id name ▼.swfDL ▼エピリストDL ▼セリフリストDL ▼エピボイスDL
1001 島村卯月
1002 小日向美穂
1003 西園寺琴歌 実装なし
1004 渋谷凛
1005 川島瑞樹
1006 上条春奈
1007 本田未央
1008 赤城みりあ
1009 大槻唯
1010 神崎蘭子
1011 三村かな子
1012 メアリー・コクラン -
1013 前川みく
1014 椎名法子
1015 宮本フレデリカ
1016 多田李衣菜
1017 佐々木千枝
1018 速水奏
1019 日野茜
1020 木村夏樹
1021 三好紗南 -
1022 安部菜々
1023 高森藍子
1024 北条加蓮
1025 城ヶ崎莉嘉
1026 兵頭レナ -
1027 高垣楓
1028 輿水幸子
1029 市原仁奈
1030 向井拓海
1031 結城晴 実装なし
1032 諸星きらり
1033 鷺沢文香
1034 櫻井桃華
1035 橘ありす
1036 姫川友紀
1037 佐久間まゆ
1038 星輝子
1039 堀裕子
1040 中野有香
1041 塩見周子
1042 相葉夕美
1043 一ノ瀬志希
1044 小早川紗枝
1045 丹羽仁美 -
1046 十時愛梨
1047 白坂小梅
1048 財前時子 -
1049 成宮由愛 -
1050 上田鈴帆
1051 緒方智絵里
1052 佐藤心
1053 神谷奈緒
1054 福山舞 -
1055 小関麗奈 実装なし
1056 工藤忍 -
1057 喜多見柚 実装なし
1058 城ヶ崎美嘉
1059 藤原肇 実装なし
1060 森久保乃々
1061 五十嵐響子
1062 道明寺歌鈴
1063 アナスタシア
1064 池袋晶葉 -
1065 荒木比奈 実装なし?
1066 新田美波
1067 ナターリア 実装なし?
1068 高峯のあ -
1069 白菊ほたる 実装なし?
1070 早坂美鈴
1071 浜口あやめ
1072 乙倉悠貴
1073 喜多日菜子 実装なし
1074 ライラ -
1075 原田美世 -
1076 和久井留美 -
1077 村上巴 実装なし
1078 八神マキノ 実装なし?
1079 大原みちる -
1080 今井加奈 -
1081 綾瀬穂乃果 -
1082 岡崎泰葉 -
1083 桃井あずき -
1084 横山千佳 -
1085 クラリス -
1086 黒川千秋 -
1087 吉岡沙紀 -
1088 ケイト -
1089 片桐早苗
1090 若林智香 -
1091 キャシー・グラハム -
1092 双葉杏
1093 三船美優
1094 イヴ・サンタクロース -
1095 桐生つかさ 実装なし
1096 依田芳乃
1097 関裕美 実装なし?
1098 水本ゆかり
1099 遊佐こずえ 実装なし
1100 鷹富士茄子 実装なし
1101 古澤頼子 -
1102 二宮飛鳥
1103 南条光 実装なし?
1104 脇山珠美
1105 村松さくら -
1106 大石泉 -
1107 土屋亜子 -
1108 冴島清美 -
1109 望月聖 実装なし
1110 奥山沙織 -
1111 浅野風香 -
1112 矢口美羽 -
1113 難波笑美
1114 大和亜季
1115 佐城雪美 実装なし
1116 並木芽衣子 -
1117 棟方愛海 実装なし?
1118 松尾千鶴 -
1119 松山久美子 -
1120 長富蓮実 -
1121 野々村そら -
1122 赤西瑛梨華 -
1123 杉坂海 -
1124 龍崎薫
1125 月宮雅 -
1126 岸部彩華 -
1127 衛藤美紗希 -
1128 藤本里奈
1129 西島櫂 -
1130 ヘレン -
1131 斉藤洋子 -
1132 真鍋いつき -
1133 浅利七海 実装なし
1134 松本沙理奈 -
1135 水木聖來 -
1136 大沼くるみ -
1137 安斎都 -
1138 小松伊吹 -
1139 梅木音葉 -
1140 栗原ネネ -
1141 涼宮星花 -
1142 及川雫
1143 古賀小春 -
1144 的場梨沙 実装なし
1145 伊集院惠 -
1146 江上椿 -
1147 相馬夏美 -
1148 槙原志保 -
1149 水野翠 -
1150 松原早耶 -
1151 柳清良 -
1152 日下部若葉 -
1153 相川千夏 -
1154 木場真奈美 -
1155 西川保奈美 -
1156 愛野渚 -
1157 仙崎恵磨 -
1158 首藤葵 -
1159 日高愛
1160 太田優 -
1161 東郷あい -
1162 松永涼
1163 高橋礼子 -
1164 柊志乃 -
1165 海老原菜帆 -
1166 篠原礼 -
1167 間中美里 -
1168 大西由里子 -
1169 小室千奈美 -
1170 秋月涼
1171 持田亜里沙 -
1172 有浦柑奈 -
1173 井村雪菜 -
1174 氏家むつみ -
1175 浜川愛結奈 -
1176 榊原里美 -
1177 柳瀬美由紀 -
1178 藤居朋 -
1179 服部瞳子 -
1180 沢田麻理菜 -
1181 水谷絵理
1182 北川真尋 -
1183 楊菲菲 -
1184 相原雪乃 -
1185 桐野アヤ -
1186 瀬名詩織 -
1187 辻野あかり 実装なし
1188 砂塚あきら 実装なし
1189 夢見りあむ 実装なし
1190 黒埼ちとせ
1191 白雪千夜
1192 久川凪
1193 久川颯

[C-1]ぷち髪型確認用

  • 実行場所
    ぷちデレラページなら多分どこでも?
  • 概要
    実行すると、所属ぷちデレラの解放済み髪型一覧が .txt でDLされます
    Excel等にそのまま貼り付けられるかと。
[C-1] ぷち解放済み髪型一覧リストDL

// そのぷちデレラの髪型開放確認APIに連続アタッチして結果をDL
(async()=>{
    // ① アタッチ用の連番定義etc一気に作成 from:1 ~to:193
    const _fromNum = 1;
    const _toNum = 193;
    if(_fromNum > _toNum){ return console.error("from-to 番号が不正かも"); }
    const _apiUrl = "https://sp.pf.mbga.jp/12008305/?guid=ON&url=http://mobamas.net/idolmaster/petit_cg/ajax_hair_styling_list_by_idol_id";
    const _dlTargetDefObj = [];
    const _petit_baseId = 1000; // 髪型開放の場合、衣装とは違い 4ケタベースっぽいので注意
    // 閉じタグエスケープ対応の為に >記号を利用。ぷち連番を10001~10193(193人分)生成
    for(let _n=_fromNum; _toNum>=_n; _n++){
        const _dlFileName = `hair_released_list_petit_id_${_petit_baseId+_n}`;
        _dlTargetDefObj.push( { "filename" : _dlFileName , "url" : _apiUrl , "optionExt" : ".jsext" , "result_key_name":_petit_baseId+_n  ,"PostObject" : { "idol_id" : (_petit_baseId+_n) }} );
    }
    const _resultMap = new Map();
    console.info("================== DL処理を開始します ==================");
    function _fileDl(_dlInfo , _index) {
        return new Promise((resolve,reject) => {
            const _fileName = _dlInfo["filename"];
            const _url = _dlInfo["url"];
            const _idol_id = _dlInfo["PostObject"]["idol_id"];
            setTimeout(async () => {
                const _formData = new FormData();
                if(_dlInfo["PostObject"]){
                    const _pObjs = _dlInfo["PostObject"];
                    for(const _keyName in _pObjs){
                        const _val = _pObjs[_keyName];
                        _formData.append( _keyName , _val );
                    }
                }
                const _hairData = await fetch(_url,{ method: "POST", mode:"no-cors" , body: _formData })
                .then(_res=>{
                    if(!_res.ok){
                        throw new Error(_res.statusText);
                    }else{
                        try{
                            // エラーページ自体(html)に繋がれる可能性もあるので、JSONパースエラーも失敗とみなす
                            const _json = _res.json();      
                            return _json;
                        }catch(_err){
                            throw new Error("JSONパース失敗");
                        }
                    }
                })
                .catch(_err=>{
                    const _resultValArr = [];
                    _resultValArr.push( "" ); // ぷち名相当
                    _resultValArr.push( "" ); // 髪型数相当
                    for(let _n=0; 9>=_n; _n++){ _resultValArr.push("") }
                    _resultMap.set( _dlInfo["result_key_name"] , _resultValArr);
                    console.error(`${_idol_id} : DLエラー(未所属判定)`, _err);
                });
                console.log(_hairData);
                // === json 取得成功と判断
                if(_hairData){
                    const _resultValArr = [];
                    const _hairArr = [];
                    const _hariList = _hairData["hair_list"];
                    let _petitName;
                    for(let _n=0; 9>=_n; _n++){ // 9回回して、10xxx~19xxx まで確認
                        const _4keta = _n;
                        let _hairIdHit = false;
                        _hariList.forEach(_item=>{
                            const _hairId = _item["hair_id"];
                            if(_4keta == _hairId[1]){ // 1X??? 4桁の値が合致
                                const _hairName = _item["hair_name"];
                                if(!_petitName){ _petitName = _hairName.split(/[+]?の髪型/).shift(); }
                                const _setFormat = `${_hairId} ${_hairName.replace(_petitName,"")}`;
                                _hairArr.push( _setFormat ); // 髪型名
                                _hairIdHit = true;
                            }
                        });
                        if(_hairIdHit == false){
                            _hairArr.push(""); // 未取得時は空白挿入
                        }
                    }
                    _resultValArr.push( _petitName );
                    _resultValArr.push( _hariList.length );
                    _resultValArr.push( ..._hairArr ); // 髪型情報を展開格納
                    _resultMap.set( _dlInfo["result_key_name"] , _resultValArr );
                }
                resolve();
            }, 100);    // DL間隔ミリ秒。あまり縮めないほうが良い
        });
    }
    // 実処理開始
    (async () => {
        await _dlTargetDefObj.reduce((promise, _dlInfo , _currentIndex) => {
            return promise.then(async () => {
                await _fileDl(_dlInfo , _currentIndex);
            });
        }, Promise.resolve());
        console.info("=== 予約中のデータを全てDLしたと判断されました ====");
        console.info(_resultMap);
        {
            let _outStr = "ぷちヘアスタイル解放状況一覧\nID / 開放済 /\n※ タブで整形されているので、Excel等にそのまま貼り付けられます\n\n";
            _resultMap.forEach((_e , _key)=>{
                //_outStr += ( "|\t" + _key + "\t|\t" + _e.join("\t|\t") ) + "\n";  // 自分用 | セパレータ含
                _outStr += ( _key + "\t" + _e.join("\t") ) + "\n";  // 一般用
            });
            console.log(_outStr);
            const _dlUrl = URL.createObjectURL( new Blob([_outStr+"\n\n"] , {type: "text/plain"}) );
            const _a = document.createElement('a');
            _a.href = _dlUrl;
            _a.download = `_ぷちヘアスタイル開放状況一覧.txt`;
            document.body.appendChild(_a);
            _a.click();
            document.body.removeChild(_a);
            URL.revokeObjectURL(_dlUrl);
        }
    })();

})();