const internalConfig = { 'network': { 'userAgent': { 'chromeWindows': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', 'curl': 'curl/8.4.0', 'curlUnity': 'UnityPlayer/2021.3.14f1 (UnityWebRequest/1.0, libcurl/7.84.0-DEV)' }, 'timeout': 15000 } }; const apiConnectDefaultHeader = { 'Access-Controll-Allow-Origin': '*' } const appSettingsStorageName = '018d2fc7-d0bb-7393-9ebc-6f6ec26b03ce_appSettings'; let appSettingsSaveData = new Object(); const appSettingsSaveDataDefault = { 'ui': { 'uiThemeMode': 'light' } }; let apiDataMasterDB = new Object(); let apiDataConfig = new Object(); //!========== ページ読み込み時に実行する処理 ========== window.addEventListener('load', async function(){ if (checkAppSettingsExistsOnStorage() === true) { loadAppSettingsFromLocalStorage(); } else { loadAppSettingsFromLocalStorage(); if (window.matchMedia('(prefers-color-scheme:dark)').matches === true) { console.warn(`prefers-color-scheme:dark detected`) appSettingsSaveData.ui.uiThemeMode = 'dark'; writeAppSettingsToLocalStorage(); } else { appSettingsSaveData.ui.uiThemeMode = 'light'; writeAppSettingsToLocalStorage(); } } appSettingsCheckedUiUpdate(); appSettingsApply(); await loadRequiredDatabase(); await decryptConfig(); pushToTrackListGroupUi(); }); document.querySelector('#loadDatabaseTestButton').addEventListener('click', async function() { console.log('loadDatabaseTestButton clicked!'); await loadRequiredDatabase(); }); document.querySelector('#trackListingTestButton').addEventListener('click', () => { console.log('trackListingTestButton clicked!'); pushToTrackListGroupUi(); }); // ========== 全てのトラックのリストをTrack Listing TestのListGroupに表示 ========== function pushToTrackListGroupUi () { const trackAllListGroup = document.querySelector('#trackAllListGroup'); while(trackAllListGroup.firstChild) { trackAllListGroup.removeChild(trackAllListGroup.firstChild); } let headerElObj = new Object(); headerElObj.buttonNodeEl = document.createElement('button'); headerElObj.divArtistNodeEl = document.createElement('div'); headerElObj.divAlbumNodeEl = document.createElement('div'); headerElObj.divTrackNodeEl = document.createElement('div'); headerElObj.buttonNodeEl.classList.add('list-group-item', 'list-group-item-action', 'd-flex'); headerElObj.buttonNodeEl.setAttribute('type', 'button'); headerElObj.buttonNodeEl.disabled = true; headerElObj.divArtistNodeEl.classList.add('flex-fill', 'w-100'); headerElObj.divAlbumNodeEl.classList.add('flex-fill', 'w-100'); headerElObj.divTrackNodeEl.classList.add('flex-fill', 'w-100'); headerElObj.divArtistNodeEl.innerHTML = 'Artist'; headerElObj.divAlbumNodeEl.innerHTML = 'Album'; headerElObj.divTrackNodeEl.innerHTML = 'Track'; headerElObj.buttonNodeEl.appendChild(headerElObj.divArtistNodeEl); headerElObj.buttonNodeEl.appendChild(headerElObj.divAlbumNodeEl); headerElObj.buttonNodeEl.appendChild(headerElObj.divTrackNodeEl); trackAllListGroup.appendChild(headerElObj.buttonNodeEl); apiDataMasterDB.response.data.albums.forEach((albumObject) => { albumObject.tracks.forEach((trackObject) => { let buttonNodeEl = document.createElement('button'); let divArtistNodeEl = document.createElement('div'); let divAlbumNodeEl = document.createElement('div'); let divTrackNodeEl = document.createElement('div'); buttonNodeEl.classList.add('list-group-item', 'list-group-item-action', 'd-flex'); buttonNodeEl.setAttribute('type', 'button'); divArtistNodeEl.classList.add('flex-fill', 'w-100'); divAlbumNodeEl.classList.add('flex-fill', 'w-100'); divTrackNodeEl.classList.add('flex-fill', 'w-100'); let albumArtistFilteredList = new Array(); albumObject.artist.forEach((str) => { albumArtistFilteredList.push(apiDataMasterDB.response.data.artists.filter((obj) => obj.uuid === str)[0].name); }); divArtistNodeEl.innerHTML = albumArtistFilteredList.join(', '); divAlbumNodeEl.innerHTML = albumObject.title; divTrackNodeEl.innerHTML = trackObject.title; buttonNodeEl.appendChild(divArtistNodeEl); buttonNodeEl.appendChild(divAlbumNodeEl); buttonNodeEl.appendChild(divTrackNodeEl); trackAllListGroup.appendChild(buttonNodeEl); }); }); } // ========== 設定画面のイベントリスナー登録など ========== document.querySelector('#settingsApplyButton').addEventListener('click', () => {appSettingsApply()}); document.querySelector('#settingsUiSwitchUiModeDiv').addEventListener('click', function () { appSettingsSaveData.ui.uiThemeMode = document.querySelector('#settingsUiSwitchUiMode').elements['settingsUiSwitchUiMode'].value; console.log('settingsUiSwitchUiMode div clicked!'); }); // ========== 設定を変更後に保存、反映させる(ボタン押したときの動作) ========== function appSettingsApply () { writeAppSettingsToLocalStorage(); switch (appSettingsSaveData.ui.uiThemeMode) { case 'light': document.querySelector('html').setAttribute('data-bs-theme', 'light'); break; case 'dark': document.querySelector('html').setAttribute('data-bs-theme', 'dark'); } } // ========== appSettingsを元に設定画面の選択状態を更新 ========== function appSettingsCheckedUiUpdate () { const settingsUiSwitchUiModeElements = document.querySelector('#settingsUiSwitchUiMode').elements; for (let i = 0; i < settingsUiSwitchUiModeElements.length; i++) { if (settingsUiSwitchUiModeElements[i].value === appSettingsSaveData.ui.uiThemeMode) { settingsUiSwitchUiModeElements[i].checked = true; } else { settingsUiSwitchUiModeElements[i].checked = false; } } } // ========== API関連の関数など ========== async function apiConnect (axiosObj) { let connectionTimerStart = performance.now(); try { const response = await axios(axiosObj); let connectionTimerEnd = performance.now(); return { 'apiConnectionTime': connectionTimerEnd - connectionTimerStart, 'response': response.data }; } catch (error) { let connectionTimerEnd = performance.now(); console.error(`API request failed: ${error.code}`); alert(`API request failed: ${error.code}`); throw error; } } async function loadRequiredDatabase () { document.querySelectorAll('.fetchingDataNowLabel').forEach((el) => { el.classList.remove('d-none'); }); apiDataMasterDB = await apiConnect({ 'method': 'get', 'url': `https://corsproxy.io/?${encodeURIComponent(`https://hf.co/datasets/DeliberatorArchiver/discography_v2_cdn/resolve/main/db/master.json`)}`, 'headers': apiConnectDefaultHeader, 'timeout': internalConfig.network.timeout }); apiDataConfig = await apiConnect({ 'method': 'get', 'url': `https://corsproxy.io/?${encodeURIComponent(`https://hf.co/spaces/DeliberatorArchiver/discography_v2_cdn_front/resolve/main/config.json`)}`, 'headers': apiConnectDefaultHeader, 'timeout': internalConfig.network.timeout }); console.log(apiDataMasterDB); console.log(apiDataConfig); if (apiDataMasterDB) { document.querySelector('#databaseTestInfoCodeEl').innerHTML = `OK (Time: ${Math.ceil(apiDataMasterDB.apiConnectionTime)} ms)`; document.querySelector('#databaseTestOutputCodeEl').innerHTML = JSON.stringify(apiDataMasterDB.response, '', ' '); } else { document.querySelector('#databaseTestInfoCodeEl').innerHTML = `Failed`; } document.querySelectorAll('.fetchingDataNowLabel').forEach((el) => { el.classList.add('d-none'); }); } // ========== MasterDB/Configの暗号化を解く ========== async function decryptConfig () { apiDataConfig.response.config.decrypted = new Object(); console.log(`Decrypting config data using AES 128-bit CBC ...`) const encryptKey = await CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(apiDataConfig.response.config.encryption.key.split('').reverse().join('')).toString(CryptoJS.enc.Utf8)); const encryptIv = await CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(apiDataConfig.response.config.encryption.iv.split('').reverse().join('')).toString(CryptoJS.enc.Utf8)); Object.keys(apiDataConfig.response.config.encrypted).forEach(async function (keyName) { apiDataConfig.response.config.decrypted[keyName] = await CryptoJS.AES.decrypt({ 'ciphertext': CryptoJS.enc.Base64.parse(apiDataConfig.response.config.encrypted[keyName]) }, encryptKey, { 'iv': encryptIv, 'mode': CryptoJS.mode.CBC, 'padding': CryptoJS.pad.Pkcs7 }).toString(CryptoJS.enc.Utf8); }); console.log(`All config data has been decrypted`); } // ========== ブラウザのLocalStorageにあるAppSettingsを読み書きする ========== function loadAppSettingsFromLocalStorage () { if (localStorage.hasOwnProperty(appSettingsStorageName)) { appSettingsSaveData = JSON.parse(CryptoJS.enc.Base64.parse(localStorage.getItem(appSettingsStorageName)).toString(CryptoJS.enc.Utf8)); console.warn(`LocalStorage key detected`); console.log(`Loaded appSettings:`); console.log(appSettingsSaveData); } else { appSettingsSaveData = appSettingsSaveDataDefault; console.warn(`LocalStorage key not found\nUsing default settings`); writeAppSettingsToLocalStorage(); } } function checkAppSettingsExistsOnStorage () { if (localStorage.hasOwnProperty(appSettingsStorageName)) { return true } else { return false } } function writeAppSettingsToLocalStorage () { // CryptoJS.enc.Utf8.parseを使うことでCryptoJS内部形式を強制 localStorage.setItem(appSettingsStorageName, CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(appSettingsSaveData)))); console.log(`Wrote appSettings:`); console.log(appSettingsSaveData); }