アイドルギャラリーやぷちデレラ等のJSONを云々
概要
モバマスのサ終によせて。
色々埋め状況をチェックしたり、保存したい方向け
- ちょっと関係ありそうなページ
前提
基本、ブラウザUA弄ってモバマス遊んでる歴戦のP向け。
PC版アプリでも DevTools
開ければいけるかも?
F12
で DevTools
を起動して、console
の箇所で各種スクリプトをコピペ実行できる方。
前置き・基礎
普通にギャラリーから開くとクエリがつくのでクソ長URLになる。
不要な部分を削るとレイアウトがおかしくなる事があるが、
それは多分ブラウザが勝手に https
で繋ごうとしているからで、 http
で明示的に繋ぎなおせば解決する
URL例:[こころに春を]高森藍子
https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fidol_gallery%2Fidol_detail%2F1cdd2ddaf342338e11ecd5fe7990466e
URL部分 idol_detail%2F
に続くハッシュがカード管理ID
アイドルオブジェクトの構成
💡 大雑把な構成
+ "detail_list" ( Array )
+ カードデータオブジェクトA
+ カードデータオブジェクトB..
+ カードデータオブジェクトC...
+ "idol_story_list" ( Array )
+ エピソードアクセスオブジェクトA
+ エピソードアクセスオブジェクトB..
+ エピソードアクセスオブジェクトA...
+ "images" ( object )
+ "bg"
+ "l"
+ "l_premium"
+ "l_nosign"
+ "l_nosign_p"
+ "l_noframe"
+ "mask_l"
+ "mask_l2"
+ "mask_l_p"
+ "mask_l2_p"
+ "xs"
+ "m2"
+ "quest"
+ "ios_app_flg"
+ "apple_review"
💡 もう少し掘り下げる
{
"detail_list":[
{
"data": {
"card_name": "[接頭辞]アイドル名",
"rarity": "レアリティ",
"cost": "コスト",
"attribute": "属性名/英語表記の模様",
"skill_name": "特技名",
"skill_effect": "説明",
"grow_idol_flag": "",
"default_attack": "攻ステ",
"default_defense": "守ステ",
"hash_card_id": "画像ハッシュ",
"alias_name": "",
"real_name": "純アイドル名(接頭辞無し)"
},
"profile": {
"card_id": "カードID数値",
"card_name": "[接頭辞]アイドル名",
"card_kana": "ひらがな表記",
"card_age": "年齢",
"card_from": "出身",
"card_height": "身長",
"card_weight": "体重",
"card_bust": "バスト",
"card_waist": "ウェスト",
"card_hip": "ヒップ",
"card_birthday": "誕生日",
"card_constellation": "星座",
"card_blood": "血液型",
"card_arm": "利き手",
"card_hobby": "趣味",
"card_cv": "担当声優"
},
"comments": {
"comment": "アイドルコメント",
"comments_my_1": "マイスタジオ",
"comments_my_2": "マイスタジオ",
"comments_my_3": "マイスタジオ",
"comments_my_4": "マイスタジオ",
"comments_my_max": "親愛度MAXセリフ",
"comments_work_1": "お仕事",
"comments_work_2": "お仕事",
"comments_work_3": "お仕事",
"comments_work_4": "お仕事",
"comments_work_max": "お仕事(親愛度MAX)",
"comments_work_love_up": "お仕事中の親愛UPセリフ",
"comments_live": "LIVEバトル",
"comments_love_max": "親愛度MAX演出"
},
"voice": {
"comment": "URL.mp4",
"comments_my_1": "URL.mp4"",
"comments_my_2": "URL.mp4"",
"comments_my_3": "URL.mp4"",
"comments_my_4": "URL.mp4"",
"comments_my_max": "URL.mp4"",
"comments_work_1": "URL.mp4"",
"comments_work_2": "URL.mp4"",
"comments_work_3": "URL.mp4"",
"comments_work_4": "URL.mp4"",
"comments_work_max": "URL.mp4"
"comments_work_love_up": "URL.mp4"",
"comments_live": "URL.mp4"",
"comments_love_max": "URL.mp4""
},
// 入手イベント。ない場合は value 自体が null
"event": {
"イベントID数値": {
"event_id": イベントID数値,
"category_id": "カテゴリID",
"event_name": "開催イベント名称"
}
},
"release": "",
"trade_prohibition": {
"is_trade_limit": false
},
// 検索用のクエリだと思われる
"idol_search_param": "keyword=エンコード済み文字列",
// 所持或いは解放
"archive": {
"normal": "1", // 通常サイン。 1 で所持経験あり
"premium": "0" // プレミアムサイン。 1 で所持経験あり
},
// アルバム登録 および 親愛度MAX経験の有無
"is_exist_archive": true,
"is_max_love": true
}
,
{
//上記のようなひと固まりのオブジェクトが以下複数個続く
}
],
// コミュエピソード系
"idol_story_list":[
{
"story_id" : "番号" ,
"story_title" : "開催ガチャ名",
"voice_enable" : { },
"flash_path" : { },
"voice_url" : { },
"open_flag" : { }, // Array または object
"icon" : { },
"movie_name_voice" : { },
},
{
//上記のようなひと固まりのオブジェクトが以下複数個続く
}
]
,
// 画像URL群
"images": {
"bg":{
},
"l":[
],
"l_premium": {
// オブジェクト key-value ペア
//数字文字列 ⇔ プレミアムサイン画像URL
},
"l_nosign": [
//url string (Emptyあり)
],
"l_nosign_p": {
// オブジェクト key-value ペア
//数字文字列 ⇔ 画像URL
},
"l_noframe": [
// 要素はフレーム無し url (空文字列/nullの可能性あり)
],
"mask_l": [
// マスク画像関係? 空文字列ばかりのケースが目立つ
],
"mask_l_p": [
// マスク画像関係? 自環境だと配列の中身は空
],
"mask_l2_p": [
// マスク画像関係? 自環境だと配列の中身は空
],
"xs": [
// 要素は正方形型ミニサイズのアイドル顔部分画像 url
// ユニットメンバーのサムネイル画像的なアレ
],
"m2": [
// 一回り小さめサイズ?なアイドルカード画像 url
// 空文字の可能性あり
]
"quest": [
// 営業イベントお仕事中に表示されるアイドルカード画像 url
// 空文字の可能性あり
]
},
"ios_app_flg": false, // 環境によりそう
"apple_review": null // 環境によりそう
}
script等
【重要】 スクリプトはいずれも、アイドルの各種個別ギャラリーページ上で実行してください
また、console.log
が潰されているので、内容物の確認は console.info
で基本代用しています。
MDN - console
個別アイドルの .json 即時ダウンロード
【実行場所】アイドルギャラリー(個人)
{
const _data = idol;
const _blob = new Blob([JSON.stringify(_data,null,'\t')],{type:'application\/json'});
const _url = URL.createObjectURL(_blob);
const _link = document.createElement("a");
const _name = idol?.detail_list[0]?.data?.real_name;
_link.href = _url;
_link.download = `${_name}.json`;
document.body.appendChild(_link);
_link.click();
document.body.removeChild(_link);
URL.revokeObjectURL(_url);
}
小ネタ / アイドルギャラリーページ
💡 アルバム未登録 / 親愛未開放数確認
【実行場所】アイドルギャラリー(個人) ※ 各アイドルギャラリーの右側に載っている情報見たほうが早い
{
const _detail_list = idol?.detail_list;
const _idolName = idol?.detail_list[0]?.profile?.card_name;
let _noArchive = 0;
let _noMaxLove = 0;
_detail_list.forEach(_e=>{
if(!_e?.is_exist_archive)
_noArchive++;
if(!_e?.is_max_love)
_noMaxLove++;
});
console.info(`${_idolName} : アルバム未登録${_noArchive}枚 / 親愛埋め未開放${_noMaxLove}枚`);
}
💡 特定の文字列を含むカード名のみオブジェクトを確認
【実行場所】アイドルギャラリー(個人) 特定のカード名に関するものだけDevTools上で眺めたければ、 例えば「新春」がカード名に入っているものだけピックアップ
{
const _detail_list = idol?.detail_list;
_detail_list.forEach(_e=>{
if(_e?.data?.card_name?.indexOf("新春") != -1){
console.dir(_e);
}
});
}
💡 エピソード情報のオブジェクトを確認
【実行場所】アイドルギャラリー(個人) エピソード(ストーリー)情報のオブジェクト一覧だけ確認 **ただし、ここで得られるエピソード再生先URLは認証情報が必要な為、 そのアカウントで実際にエピソードを開放していないと、アクセスしても見られない。** しかもここで得られるURLは、後の内部処理で書き換えられる事が前提になっている模様。
{
const _detail_list = idol?.idol_story_list;
_detail_list.forEach(_e=>{
console.info(_e);
console.info(_e?.story_title);
console.info(_e?.flash_path);
});
}
💡 アルバム親愛埋め状況をテーブルとして確認
【実行場所】アイドルギャラリー(個人)
{
const _tableObj = {};
idol?.detail_list?.forEach(_e=>{
const _valueObj = {};
let _unArchiveCount = 0;
if(!_e?.is_exist_archive){
_unArchiveCount++;
}
if(!_e?.is_max_love){
_unArchiveCount++;
}
const _acvDef = ["💯","👍","💦"];
const _rareDef = ["?" , "N","N+","R","R+","SR","SR+"];
_valueObj["コスト"] = _e?.data?.cost;
_valueObj["レア"] = _rareDef[_e?.data?.rarity];
_valueObj["埋め"] = _acvDef[_unArchiveCount];
_valueObj["アルバム登録"] = _e?.is_exist_archive?"📗":null;
_valueObj["親愛度MAX"] = _e?.is_max_love?"💗":null;
_valueObj["hash"] = _e?.data?.hash_card_id;
_tableObj[_e?.data?.card_name] = _valueObj;
});
console.table(_tableObj);
}
💡 DL可能なボイス数などを Console.table() で確認
【実行場所】アイドルギャラリー(個人) 色々情報を載せようとしてかなりカオスなコードに...
{
const _tableInfoObj = {};
let _unDlVoiceSum = 0;
let _dlVoiceSum = 0;
const _detail_list = idol?.detail_list;
for(const _index in _detail_list){
const _e = _detail_list[_index];
// そのカードの cv 情報で判断
let _noVoice = false;
if(_e?.profile?.card_cv == null){
_noVoice = true;
}
const _comments = _e?.comments;
const _tableValueObj = {};
let _dlVoiceCount = 0;
let _unDlVoiceCount = 0;
_tableValueObj["全回収可能?"] = null;
_tableValueObj["ボイス実装の有無"] = (_noVoice?"未実装":"👌");
const _voices = _e?.voice;
let _commentreleasedCount = 0;
let _allCommentRelease = true;
for(const _v in _voices){
if(_comments[_v] === "???"){
// コメント未開放
_allCommentRelease = false;
if(!_noVoice){
_unDlVoiceSum++;
_unDlVoiceCount++;
}
}
else{
_commentreleasedCount++;
if(!_noVoice){
_dlVoiceCount++;
}
}
}
const _voiceAllCount = Object.keys(_voices).length;
_dlVoiceSum += _dlVoiceCount;
_tableValueObj["DL出来ない未開放ボイス数"] = _noVoice?"未実装":(_unDlVoiceCount);
_tableValueObj["DL可能ボイス数"] = _noVoice?"未実装":(_dlVoiceCount);
_tableValueObj["コメント開放数"] = _commentreleasedCount;
_tableValueObj["コメント未開放数"] = _voiceAllCount - _commentreleasedCount;
if(_noVoice){
_tableValueObj["全回収可能?"] = "未実装";
}
else{
_tableValueObj["全回収可能?"] = (_allCommentRelease&&!_noVoice)?"👍全開放":`${_commentreleasedCount}個開放`;
}
_tableInfoObj[_e?.data?.card_name] = _tableValueObj;
}
console.table(_tableInfoObj);
console.info(`全カード数 ${Object.keys(_tableInfoObj).length}`);
console.info(`DL可能なボイス合計 ${_dlVoiceSum} / 実装済だが未開放でDL出来ないボイス合計 ${_unDlVoiceSum}`);
}
ボイスファイル一括(順次)DL
【実行場所】アイドルギャラリー(個人)
本丸。 公開しておいて何ですが、用法容量を守って適切にご利用ください...
泥臭い処理ですがユルシテ
中断はページ上でEsc
キーを押せばOKなハズ。
_dlResumeIndex
はレジューム(途中からDLする)機能相当。
DL予定となるファイル数序列の数字を代入しておくと、
その序列以前にDLするはずだったファイルを全てスキップして、連番途中から開始する
予約DLがすべて完了した場合、任意のオーディオファイルを鳴らす機能も加えました。
DLファイル名は [カード名]_[コメント役割を示す英語のキー名].mp4
的な感じにしています。
後からリネームするのクッソ面倒だと思うんで、
オブジェクトとか眺めて改造できるなら、前もって任意でフォーマット変えておいた方が良いかもしれません
💡 ボイスファイル一括(順次)DL / クリックで展開
{
// レジューム機能 ( DL予約一覧の序列番号を代入 )
// 0 だと最初から
const _dlResumeIndex = 0;
const _dlTargetDefObj = [];
let _unDlVoiceSum = 0;
let _dlVoiceSum = 0;
window.voiceFileDlStop = false;
const _detail_list = idol?.detail_list;
for(const _index in _detail_list){
const _e = _detail_list[_index];
let _noVoice = false;
if(_e?.profile?.card_cv == null){
_noVoice = true;
}
const _comments = _e?.comments;
const _voices = _e?.voice;
for(const _v in _voices){
if(_comments[_v] === "???"){
if(!_noVoice){
_unDlVoiceSum++;
}
}
else{
if(!_noVoice){
_dlVoiceSum++;
const _url = _voices[_v];
// ファイル名基本フォーマット (拡張子除く)
const _saveFileFormatName = `${_e?.data?.card_name}_${_v}`;
let _isSkip = false;
if(_dlResumeIndex > _dlVoiceSum){
_isSkip = true;
}
_dlTargetDefObj.push( { "filename" : _saveFileFormatName , "url" : _url , "skip" : _isSkip} );
}
}
}
}
console.info("=================================================");
console.info("DL処理を開始します。 中止したい場合はページ上で Esc(エスケープキー) を押してください");
console.info("=================================================");
console.info(`DL予定のボイス数合計 : ${_dlVoiceSum}`);
console.info(`DL出来ない未開放コメントの実装済ボイス数合計 ${_unDlVoiceSum}`)
function _fileDl(_dlInfo , _index) {
return new Promise((resolve,reject) => {
const _fileName = _dlInfo.filename;
const _url = _dlInfo.url;
const _skip = _dlInfo.skip;
if(window.voiceFileDlStop){
console.info("以降のDLを全て中止します");
reject();
return;
}
if(_skip){
console.info(`${_fileName} (${_index+1}個目) のDLをスキップします`);
resolve();
return;
}
setTimeout(() => {
const _req = new XMLHttpRequest();
_req.open("GET", _url , true);
_req.responseType = "blob";
_req.onload = function(oEvent) {
const blob = _req.response;
const _dlUrl = URL.createObjectURL(blob);
const _ext = _url.split('.').pop();
console.info(`${_dlInfo.filename} : ${blob.size} byte`);
{
const _a = document.createElement('a');
_a.href = _dlUrl;
// ファイル名+拡張子
_a.download = `${_fileName}.${_ext}`;
document.body.appendChild(_a);
_a.click();
document.body.removeChild(_a);
URL.revokeObjectURL(_dlUrl);
console.info(`${_index+1} 個目のDLが完了しました`);
}
resolve();
};
_req.send();
}, 500); // DL間隔ミリ秒。あまり縮めないほうが良い
});
}
(async () => {
await _dlTargetDefObj.reduce((promise, _dlInfo , _currentIndex) => {
return promise.then(async () => {
await _fileDl(_dlInfo , _currentIndex);
});
}, Promise.resolve());
console.info("=== 予約中のボイスデータを全てDLしたと判断されました ====");
// 全ての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.voiceFileDlStop = true;
}
});
}
Promise
の順次実行コードにかなり助けられました。 感謝✨
Zenn > wintyo > 配列のPromiseを直列で実行する方法
一部演出ページのFlashを自動名前付けDL
【実行場所】エピソード再生ページ
一般的な(古めの)ストーリー再生ページで実行すると、
.swf
で作成された物ならファイル名付きでDL可能なはず。
アイドルギャラリーから観られる(古い)エピソードを始め、シンデレラヒストリーにも一応対応させている筈
( シンデレラヒストリーも古めの物でないと.swfとしてDL出来ない )
ぷちエピソードは未対応。専用処理として別に組んでいます
DL時のファイル命名、適当に弄ってください。
[アイドル名]_[ストーリーID]_[エピソード話数].swf
[アイドル名]_[ストーリーID]_[エピソード話数]_voice.swf
【※】hash として扱われているものは、そのエピソードアイドルを開く直前にアクセスしたカードの実装カードハッシュな模様
※ APIを挟む事で、初期ハッシュからアイドル名にファイルフォーマットを変更 ※ ボイスアタッチ有無の形式に対応
(async function(){
// [1] .swf DL用のURL抜き出し(恐らく認証クエリ付き)
let _swfUrl;
[...document.scripts].some((_val,_index)=>{
if(_val.textContent.length > 200){ //一定数の長さで弾く(雑)
let _pexMatchArr = _val.textContent.match(/new Pex\('(https:[^']*)'/);
if(_pexMatchArr){
_swfUrl = _pexMatchArr[1];
}
}
});
if(!_swfUrl){
console.info("【!】.swfのDLが出来ないタイプと判断されました");
return;
}
// [2] 自動DL時、ファイル名を付けるための処理。
// URLからアイドルハッシュ,ストーリー管理番号,エピソード番号を抽出しつつ、.swfとしてDL可能か(こちらでも)精査
// ガチャRとかの複数エピは後半が %3F だった模様
const _dlFlashMatch = location.href.match(/flashSsSs(?<story_id>[0-9]{1,})SsSs(?<episode>[0-9]{1,})SsSs[0-9]SsSsidol_gallery--idol_detail--(?<hash>.*)--[0-9]SsSs(?<voice>[0-9]?)%(2F|3F)/);
let _isCinHis , _doSwfDL , _isVoiceEnable = false;
let _paramObj;
if(_dlFlashMatch){
_paramObj = _dlFlashMatch?.groups;
_doSwfDL = true;
_isVoiceEnable = _paramObj?.voice > 0;
}
else{
const _dlInfoMatch = location.href.match(/idol_story%2F(?<type>[^%]+)%2F(?<story_id>[0-9]{1,})%2F(?<episode>[0-9]{1,})[^--].+--idol_detail--(?<hash>.*)--[0-9]%2F(?<voice>[0-9])%/);
if(_dlInfoMatch){
_paramObj = _dlInfoMatch?.groups;
if(_paramObj?.type == "cjs"){
_doSwfDL = true;
_isVoiceEnable = _paramObj?.voice > 0;
}
}
// シンデレラヒストリーでは 3から始まる7桁?のIDをストーリーIDとして処理 例->3000005
const _dlCinHisMatch = location.href.match(/play_episodeSsSs([0-9]{1,})SsSs(?<story_id>[0-9]{1,})SsSs[0-9]+SsSs/);
if(_dlCinHisMatch){
_isCinHis = true;
_paramObj = _dlCinHisMatch?.groups;
console.info("シンデレラヒストリー再生ページと判断");
_doSwfDL = true;
}
}
if(!_paramObj){
console.info("【!】URLを用いたDLパラメタの生成に失敗");
return;
}
//自前API挟んでアイドル名取得
const _idolJson = await fetch(`https://mkt.packetroom.net/idoldata/?hash=${_paramObj?.hash}`).then(res => res.json());
console.info(`${_idolJson?.name} / ${_paramObj?.hash}`);
const _saveIdolName = `${_idolJson?.name?.replace(/\[[^\]]*\]/g,"").replace("+","")}`; // [肩書]と+ を除去
console.info(`${_idolJson?.name} → ${_saveIdolName}`);
if(_doSwfDL){ // .swf DL
const _blob = await fetch(_swfUrl).then(response => response.blob());
if(_blob.type.indexOf("flash") != -1){
const _dlUrl = URL.createObjectURL(_blob);
const _a = document.createElement('a');
_a.href = _dlUrl;
if(_isCinHis){ // シンデレラヒストリー用
_a.download = `cinderella_history_${_paramObj?.story_id}`;
}else{ // (旧:アイドル実装初期キャラハッシュ)→(改:アイドル名)+ストーリーID+エピソード数(前,あれば中,後編)+拡張子
//_a.download = `${_paramObj?.hash}_${_paramObj?.story_id}_${_paramObj?.episode}`;
_a.download = `${_saveIdolName}_${_paramObj?.story_id}_${_paramObj?.episode}`;
}
if(_isVoiceEnable){
_a.download += `_voice`;
}
if(_a.download){
_a.download += `.swf`;
}
document.body.appendChild(_a);
_a.click();
document.body.removeChild(_a);
URL.revokeObjectURL(_dlUrl);
}
}
else{
console.info("【!】.swfではDL出来ないタイプと判断されました。");
// ここに .swf でDL出来なかった以外の処理を仕込んでもOK
}
console.info(_paramObj);
console.info(`DL動作 : ${_doSwfDL}`);
})();
💡 小ネタ / アイドルエピソード(仮称)のURL 3パターン
アイドルエピソード(仮称) を開くURLには、確認している限り3つパターンがある模様。 以下パターン例
+ **smart_phone_flash/convert/idol_story**
+ **古めのDL可能なFlash(.swf)で用いられる。 DL可 👍**
+ [放課後サマー]高森藍子 (放課後サマー / 前+後編)
+ [春、神和ぎ]渋谷凛 (新春!光り満ちる振袖 / 前編)
+ [シンデレラガール]渋谷凛 (シンデレラガール渋谷凛 / 前+後編)
+ [楽園の佳景]藤原肇 (かがやく盛夏♪パラダイスサマー / 前+後編)
+ **idol_story/generator_cjs**
+ **疑似Flashエピソードにおける「前編」で多く用いられる。 DL可 👍**
+ [水辺の彩花]櫻井桃華 (水辺の彩花 / 前編)
+ [内緒の休息日]高森藍子 (鮮やかに色づいて 癒しの露天ツアー / 前編)
+ [聖夜の秘めごと]藤原肇 (10周年記念クリスマスパーティー / 前編)
+ **idol_story/cjs**
+ **疑似Flashエピソードにおける「後編」で多く用いられる。 .swf DL不可 💦**
+ [内緒の休息日]高森藍子 (鮮やかに色づいて 癒しの露天ツアー / 後編)
+ [ちいさな手のひら]佐城雪美 (ちいさな手のひら / 後編)
【ぷちデレラ】
ぷち衣装 解放済み一覧 .json 即時DL
解放(所持)済みぷち衣装の情報をDL
.json
形式の筈
【実行場所】ぷちデレラTOP(とか?)
$.ajax({
type: 'POST',
url: 'https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2F125.6.169.35%2Fidolmaster%2Fpetit_cg%2Fajax_coordinate_list',
dataType: 'json',
data: {
"type": "0",
"deck_position": "1"
}
}).done(
function(data) {
var _orgiList = data.accessory_list;
var _putArr = [];
for(var n in _orgiList){
_orgiList[n].info["data_memo"] = "ajax_coordinate_list";
_putArr.push(_orgiList[n].info);
}
var content = JSON.stringify(_putArr , null, "\t"); //整形
var blob = new Blob([ content ], { "type" : "application\/json" });
var _link = document.createElement("a");
_link.href = window.URL.createObjectURL(blob);
_link.download = "petit.json";
_link.target = "_blank";
document.body.appendChild(_link);
_link.click();
document.body.removeChild(_link);
}
);
ぷちセリフ一覧 .json 即時DL
【実行場所】ぷちプロフ(個人)
ぷちデレラ Lv30、かつテクニカルボードを全て開放していれば 取得可能。
( この条件が満たされてなければ null
となって得られないぽい )
[ぷちID]_[ショートアイドル名]_petit_serif.json
{
const _data = comment_list;
const _blob = new Blob([JSON.stringify(_data,null,'\t')],{type:'application\/json'});
const _url = URL.createObjectURL(_blob);
const _link = document.createElement("a");
const _name = mc.character['1'].short_name;
const _petitId = mc.character['1'].idol_id;
_link.href = _url;
_link.download = `${_petitId}_${_name}_petit_serif.json`;
document.body.appendChild(_link);
_link.click();
document.body.removeChild(_link);
URL.revokeObjectURL(_url);
}
💡 小ネタメモ / セリフオブジェクト
各ぷちデレラ個人(?)の「ぷちプロフ」ページ上にて、 `comment_list` ( window.comment_list 格納) のconsole実行でセリフ情報が確認できる。 以下、`comment_list` から得られるセリフテキストの雑な趣旨抜粋
+ top (ぷちTOP)
+ Lv1~10 → 5種?
+ Lv1~30 → 7種?
+ Lv11~30 → 4種?
+ Lv11~20 → 4種?
+ Lv21~30 → 13種?
+ lesson (ぷちレッスン)
+ Lv1~10
+ ベースレッスン → 1種?
+ ぷちレッスン → 1種?
+ テクニカルレッスン Vo → 1種?
+ テクニカルレッスン Da → 1種?
+ テクニカルレッスン Vi → 1種?
+ Lv1~30
+ ベースレッスン → 2種?
+ テクニカルレッスン Vo → 1種?
+ テクニカルレッスン Da → 1種?
+ テクニカルレッスン Vi → 1種?
+ Lv11~20
+ ベースレッスン → 2種?
+ ぷちレッスン → 1種?
+ テクニカルレッスン Vo → 1種?
+ テクニカルレッスン Da → 1種?
+ テクニカルレッスン Vi → 1種?
+ Lv21~30
+ ベースレッスン → 2種?
+ ぷちレッスン → 1種?
+ テクニカルレッスン Vo → 2種?
+ テクニカルレッスン Da → 2種?
+ テクニカルレッスン Vi → 2種?
+ shop (ぷちショップ)
+ Lv1~30 → 1種?
+ coordinate (コーディネート)
+ Lv1~30 → 2種?
+ tech_board (テクニカルボード)
+ 開放コメント
+ Lv1~30 → 6種?
+ テクニカルボード
+ Lv1~30 → 3種?
ぷちエピソードの .swf を順次DL
【実行場所】ぷちプロフ(個人)
DLフォーマットは2つ。 ボイスオプションがある場合は、後者のフォーマットもDL
[ぷちID]_[ショートアイドル名]_[エピソード話数].json
[ぷちID]_[ショートアイドル名]_[エピソード話数]_voice.json
{
(async()=>{
// ① APIからそのアイドルのぷちエピソード一覧取得
const _url = "https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fpetit_cg%2Fajax_episode_list";
const _petitId = mc.character['1']?.idol_id;
const _name = mc.character['1']?.short_name;
const _formData = new FormData();
_formData.append('idol_id', _petitId);
const _epData = await fetch(_url,{ method: "POST",body: _formData
}).then(_res=>{
return _res.json();
});
console.info(_epData);
// ② 一覧を精査し、ファイル名フォーマットなどを一気に決める
const _dlTargetDefObj = [];
const _ep_list = _epData?.episode_list;
for(const _index in _ep_list){
const _e = _ep_list[_index];
const _epNum = _e?.episode_id;
const _epName = _e?.episode_name;
const _epVoice = _e?.episode_voice;
// ファイル名基本フォーマット (拡張子除く)
let _saveFileFormatName = `${_petitId}_${_name}_${_epNum}`;
// ボイス無しURL
{
const _voiceOffSwfUrl = `https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fsmart_phone_flash`
+`%2Fconvert%2Fpetit_cgSsSspetit_show_episode_swfSsSs${_petitId}SsSs${_epNum}SsSscoordinate_idolSsSs1%2F0%2F0%2F0%2F1%2F0%2F0%2F0%2F1`
_dlTargetDefObj.push( { "filename" : _saveFileFormatName , "url" : _voiceOffSwfUrl } );
}
// ボイスオプションが存在する場合「あり版」も追加でセット。 判定はこの条件でOK?
if(_epVoice && _epVoice > 0){
_saveFileFormatName += "_voice";
const _voiceOnSwfUrl = `https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fsmart_phone_flash`
+`%2Fconvert%2Fpetit_cgSsSspetit_show_episode_swfSsSs${_petitId}SsSs${_epNum}SsSscoordinate_idolSsSs1%2F0%2F0%2F0%2F1%2F${_epVoice}%2F0%2F0%2F1`
_dlTargetDefObj.push( { "filename" : _saveFileFormatName , "url" : _voiceOnSwfUrl } );
}
}
console.info("=================================================");
console.info("DL処理を開始");
console.info("=================================================");
console.info(_dlTargetDefObj);
// ③ 先にDL処理を定義
function _fileDl(_dlInfo , _index) {
return new Promise((resolve,reject) => {
const _fileName = _dlInfo.filename;
const _url = _dlInfo.url;
setTimeout(() => {
const _req = new XMLHttpRequest();
_req.open("GET", _url , true);
_req.responseType = "blob";
_req.onload = function(oEvent) {
const blob = _req.response;
const _dlUrl = URL.createObjectURL(blob);
console.info(`${_dlInfo.filename} : ${blob.size} byte`);
{
const _a = document.createElement('a');
_a.href = _dlUrl;
// ファイル名+拡張子
_a.download = `${_fileName}.swf`;
document.body.appendChild(_a);
_a.click();
document.body.removeChild(_a);
URL.revokeObjectURL(_dlUrl);
console.info(`${_index+1} 個目のDLが完了しました`);
}
resolve();
};
_req.send();
}, 500); // DL間隔ミリ秒。あまり縮めないほうが良い
});
}
// ④ DL用定義を用いて順次DL
(async () => {
await _dlTargetDefObj.reduce((promise, _dlInfo , _currentIndex) => {
return promise.then(async () => {
await _fileDl(_dlInfo , _currentIndex);
});
}, Promise.resolve());
console.info("=== ぷちエピソードのswfを全てDLしたと判断されました ====");
// 全ての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);
});
});
}
})();
})();
}
💡 小ネタメモ / ぷちエピソード一覧 .json 即時DL
【実行場所】ぷちプロフ(個人) **ぷちデレラID_ショートアイドル名_petit_episode.json** ※ 大した情報は含まれていない。
{
(()=>{
const _petitId = mc.character['1']?.idol_id;
const _name = mc.character['1']?.short_name;
$.ajax({
type: 'POST',
url: 'https://sp.pf.mbga.jp/12008305/?guid=ON&url=http%3A%2F%2Fmobamas.net%2Fidolmaster%2Fpetit_cg%2Fajax_episode_list%3Fl_frm%3DPetit_cg_coordinate_idol_1%26rnd%3D933716054',
dataType: 'json',
data: {
'idol_id': _petitId
}
})
.done((data)=>{
console.info(data);
// 確認だけしたければここを無効に
if(false){
const _data = data;
const _blob = new Blob([JSON.stringify(_data,null,'\t')],{type:'application\/json'});
const _url = URL.createObjectURL(_blob);
const _link = document.createElement("a");
_link.href = _url;
_link.download = `${_petitId}_${_name}_petit_episode.json`;
document.body.appendChild(_link);
_link.click();
document.body.removeChild(_link);
URL.revokeObjectURL(_url);
}
});
})();
}
+ ぷちエピソードURL
+ `https://(略)mobamas.net%2Fidolmaster%2Fsmart_phone_flash%2Fconvert%2Fpetit_cgSsSspetit_show_episode_swfSsSs[ぷちデレラアイドルID]SsSs[エピソード話数]SsSscoordinate_idolSsSs1%2F0%2F0%2F0%2F1%2F[ボイスあり1なし0]%2F0%2F0%2F1%3F`
+ アカウントの解放実績に紐づけられているっぽく、フォーマットに則ったURLでエピソードにアクセスしてもエラる模様
小ネタメモ / ajax API
プチページ上のソースを $.ajax({
とかで検索
テクニカルボード上にて window.path
を Console 実行でも、なんかAPIぽいの?見られました
- ぷちプロフ
%2Fpetit_cg%2Fajax_try_on_by_idol_id%3F
%2Fpetit_cg%2Freport_ajax_error%3F
%2Fpetit_cg%2Fajax_coordinate_list_by_idol_id%3F
%2Fpetit_cg%2Fajax_coordinate_by_idol_id%3F
%2Fpetit_cg%2Fajax_hair_styling_list_by_idol_id%3F
%2Fpetit_cg%2Fajax_hair_styling_by_idol_id%3F
%2Fpetit_cg%2Fajax_episode_list%3F
%2Fpetit_cg%2Fajax_get_show_unit_data%3F
%2Fpetit_cg%2Fajax_edit_unit_name%3F
%2Fpetit_cg%2Fajax_edit_background%3F
- ぷち衣装リスト
%2Fpetit_cg%2Fajax_sell_accessory%3F
売却?%2Fpetit_cg%2Fajax_lock_accessory%3F
保護?%2Fpetit_cg%2Fajax_unlock_accessory%3F
保護解除?%2Fpetit_cg%2Fajax_get_show_unit_data%3F
ユニット情報取得?%2Fpetit_cg%2Fajax_edit_unit_name%3F
ユニット名編集? (ぷちめにゅー → ユニット名変更)%2Fpetit_cg%2Fajax_edit_background%3F
背景変更? (ぷちめにゅー → 背景変更)
小ネタメモ / テクニカルボード
Console上で window.data
実行
window.data.idolId
ぷちデレラアイドルID
window.data.idolIdCenter
現在のぷちユニットセンター
window.data.idolIdLeft
現在のぷちユニットレフト
window.data.idolIdRight
現在のぷちユニットライト
他、ボードに関する情報などが盛りだくさん。
プレイヤー固有の ぷちデレラお迎え時期が記載されている可能性アリ (あるいは実装日?)
知り合いの方・フォロワーさんで「実行したけどどうもうまくいかんかったわ」みたいなトコあったら、
何らかの形で教えてください (雑な意思表示)
他適当に随時更新するかもしれません。