From 146bf42bf544d3ad09e43f402546cf061ffe7717 Mon Sep 17 00:00:00 2001 From: Pixkk <30828435+pixkk@users.noreply.github.com> Date: Sun, 14 May 2023 18:54:42 +0300 Subject: [PATCH 1/5] Delete node.js.yml --- .github/workflows/node.js.yml | 115 ---------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 .github/workflows/node.js.yml diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index 9327cf8..0000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: CI - -on: - push: - branches: - - main - - dev - -env: - NODE_VERSION: 16 - -jobs: - build: - name: Build web assets - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm i; cd NUXT; npm i - - name: Set App Version - working-directory: NUXT - run: sed -i 's/dev-local/${{ github.sha }}/' nuxt.config.js - - name: Build web assets - working-directory: NUXT - run: npm run generate - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: dist - path: dist - android: - name: Build Android platform - runs-on: ubuntu-latest - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: dist - path: dist - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm i - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Copy web assets to native platform - run: npx cap copy android - - name: Update native platform - run: npx cap update android - - name: Build with Gradle - working-directory: android - run: chmod +x gradlew; ./gradlew clean assembleRelease -x test -Pandroid.injected.signing.store.file=/home/runner/work/VueTube/VueTube/android/key.jks -Pandroid.injected.signing.store.password=${{ secrets.ANDROID_STORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.ANDROID_KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.ANDROID_KEY_PASSWORD }} - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: android - path: android/app/build/outputs/apk/release/app-release.apk - - ios: - name: Build iOS platform - runs-on: macos-latest - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: dist - path: dist - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm i - - name: Copy web assets to native platform - run: npx cap copy ios - - name: Update native platform - run: npx cap update ios - - name: Add empty `GoogleService-Info.plist` - run: echo "$GOOGLE_SERVICE_INFO_PLIST" > ios/App/App/GoogleService-Info.plist - env: - GOOGLE_SERVICE_INFO_PLIST: ${{secrets.GOOGLE_SERVICE_INFO_PLIST}} - - name: Build and archive with xcodebuild - working-directory: ios - run: xcodebuild - -workspace App/App.xcworkspace - -scheme App - -archivePath App/build/App.xarchive - clean build archive - CODE_SIGN_IDENTITY="" - CODE_SIGNING_REQUIRED=NO - CODE_SIGNING_ALLOWED="NO" - CODE_SIGN_ENTITLEMENTS="" - - name: Make IPA - run: mkdir Payload && mv ~/Library/Developer/Xcode/DerivedData/App-*/Build/Products/Debug-iphoneos/App.app/ Payload && zip -r Payload.zip Payload && mv Payload.zip VueTube.ipa - - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: iOS - path: VueTube.ipa From 59aa3051c9ab74c55ccdc6eba68fd6d2265a13b5 Mon Sep 17 00:00:00 2001 From: Georgiy Date: Sun, 14 May 2023 18:51:03 +0300 Subject: [PATCH 2/5] fix(video): Fixed player (loading music video), added publishDate information in metadata section of video info. --- .../videoWithContextRenderer.vue | 16 ++- NUXT/plugins/innertube.js | 102 +++++++++++++++++- 2 files changed, 112 insertions(+), 6 deletions(-) diff --git a/NUXT/components/gridRenderers/videoWithContextRenderer.vue b/NUXT/components/gridRenderers/videoWithContextRenderer.vue index a353d73..5d88cde 100644 --- a/NUXT/components/gridRenderers/videoWithContextRenderer.vue +++ b/NUXT/components/gridRenderers/videoWithContextRenderer.vue @@ -30,12 +30,20 @@ export default { computed: { thumbnailOverlayText() { - return this.video.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer - .text.runs[0].text; + this.video.thumbnailOverlays.forEach((thumbnail) => { + if (thumbnail.thumbnailOverlayTimeStatusRenderer) { + return thumbnail.thumbnailOverlayTimeStatusRenderer.text.runs[0].text; + } + }); + return ""; }, thumbnailOverlayStyle() { - return this.video.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer - .style; + this.video.thumbnailOverlays.forEach((thumbnail) => { + if (thumbnail.thumbnailOverlayTimeStatusRenderer) { + return thumbnail.thumbnailOverlayTimeStatusRenderer.style; + } + }); + return "DEFAULT"; }, }, diff --git a/NUXT/plugins/innertube.js b/NUXT/plugins/innertube.js index 08f4192..78fa57b 100644 --- a/NUXT/plugins/innertube.js +++ b/NUXT/plugins/innertube.js @@ -8,7 +8,7 @@ import { Http } from "@capacitor-community/http"; import { getBetweenStrings, delay } from "./utils"; import rendererUtils from "./renderers"; -import constants from "./constants"; +import constants, { YT_API_VALUES } from "./constants"; class Innertube { //--- Initiation ---// @@ -17,6 +17,7 @@ class Innertube { this.ErrorCallback = ErrorCallback || undefined; this.retry_count = 0; this.playerParams = ""; + this.signatureTimestamp = 0; } checkErrorCallback() { @@ -28,9 +29,82 @@ class Innertube { url: constants.URLS.YT_URL, params: { hl: "en" }, }).catch((error) => error); + // Get url of base.js file + const baseJsUrl = + constants.URLS.YT_URL + + getBetweenStrings(html.data, '"jsUrl":"', '","cssUrl"'); + try { if (html instanceof Error && this.checkErrorCallback) this.ErrorCallback(html.message, true); + // Get base.js content + const baseJs = await Http.get({ + url: baseJsUrl, + }).catch((error) => error); + // Example: + //;var IF={k4:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}, + // VN:function(a){a.reverse()}, + // DW:function(a,b){a.splice(0,b)}}; + let isMatch; + if ( + /;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z]+:function\(a\)\{[^}]*\},\n[A-Za-z]+:function\([^)]*\)\{[^}]*\}\};/.exec( + baseJs.data + ) + ) { + isMatch = + /;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z]+:function\(a\)\{[^}]*\},\n[A-Za-z]+:function\([^)]*\)\{[^}]*\}\};/.exec( + baseJs.data + ); + } else if ( + /;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z]+:function\([A-Za-z],[A-Za-z]\)\{[^}]*\},\n[A-Za-z]+:function\([^)]*\)\{[^}]*\}\};/.exec( + baseJs.data + ) + ) { + isMatch = + /;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z]+:function\([A-Za-z],[A-Za-z]\)\{[^}]*\},\n[A-Za-z]+:function\([^)]*\)\{[^}]*\}\};/.exec( + baseJs.data + ); + } + + if (isMatch) { + console.log("The input string matches the regex pattern."); + } else { + console.log("The input string does not match the regex pattern."); + } + // Get first part of decipher function + const firstPart = isMatch[0].substring(1); + + if ( + /\{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return [A-Za-z]\.join\(""\)\};/.exec( + baseJs.data + ) + ) { + isMatch = + /\{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return [A-Za-z]\.join\(""\)\};/.exec( + baseJs.data + ); + } else if ( + /{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);return +[A-Za-z]\.join\(""\)};/.exec( + baseJs.data + ) + ) { + isMatch = + /{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);[A-Za-z]+\.[A-Za-z]+\([^)]*\);return +[A-Za-z]\.join\(""\)};/.exec( + baseJs.data + ); + } + // Example: + // {a=a.split("");IF.k4(a,4);IF.VN(a,68);IF.DW(a,2);IF.VN(a,66);IF.k4(a,19);IF.DW(a,2);IF.VN(a,36);IF.DW(a,2);IF.k4(a,41);return a.join("")}; + + // Get second part of decipher function + const secondPart = + "var decodeUrl=function(a)" + isMatch[0] + "return decodeUrl;"; + let decodeFunction = firstPart + secondPart; + let decodeUrlFunction = new Function(decodeFunction); + this.decodeUrl = decodeUrlFunction(); + let signatureIntValue = /.sts="[0-9]+";/.exec(baseJs.data); + // Get signature timestamp + this.signatureTimestamp = signatureIntValue[0].replace(/\D/g, ""); try { const data = JSON.parse( "{" + getBetweenStrings(html.data, "ytcfg.set({", ");") @@ -205,7 +279,7 @@ class Innertube { autoCaptionsDefaultOn: false, autonavState: "STATE_NONE", html5Preference: "HTML5_PREF_WANTS", - signatureTimestamp: 19473, + signatureTimestamp: this.signatureTimestamp, referer: "https://m.youtube.com/", lactMilliseconds: "-1", watchAmbientModeContext: { @@ -355,6 +429,7 @@ class Innertube { const responseInfo = response.data.output; const responseNext = response.data.outputNext; const details = responseInfo.videoDetails; + const publishDate = responseInfo.microformat.playerMicroformatRenderer.publishDate; // const columnUI = // responseInfo[3].response?.contents.singleColumnWatchNextResults?.results // ?.results; @@ -378,6 +453,28 @@ class Innertube { this.playerParams = ownerData.navigationEndpoint.watchEndpoint.playerParams; } catch (e) {} + // Deciphering urls + resolutions.formats + .concat(resolutions.adaptiveFormats) + .forEach((source) => { + if (source.signatureCipher) { + const params = new Proxy( + new URLSearchParams(source.signatureCipher), + { + get: (searchParams, prop) => searchParams.get(prop), + } + ); + if (params.s) { + let cipher = decodeURIComponent(params.s); + let decipheredValue = this.decodeUrl(cipher); + // console.log("decipheredValue", decipheredValue); + source["url"] = (params.url + "&sig=" + decipheredValue).replace( + /&/g, + "&" + ); + } + } + }); const vidData = { id: details.videoId, title: details.title, @@ -391,6 +488,7 @@ class Innertube { availableResolutions: resolutions?.formats, availableResolutionsAdaptive: resolutions?.adaptiveFormats, metadata: { + publishDate: publishDate, contents: vidMetadata.contents, description: details.shortDescription, thumbnails: details.thumbnails?.thumbnails, From 58db12e7965f3e2a5d1433c0aaf36ad838aeb0a3 Mon Sep 17 00:00:00 2001 From: Georgiy Date: Sun, 14 May 2023 18:51:33 +0300 Subject: [PATCH 3/5] fix(description): Fixed errors in description --- .../UtilRenderers/YtTextFormatterNew.vue | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/NUXT/components/UtilRenderers/YtTextFormatterNew.vue b/NUXT/components/UtilRenderers/YtTextFormatterNew.vue index 52c73b1..0d74a45 100644 --- a/NUXT/components/UtilRenderers/YtTextFormatterNew.vue +++ b/NUXT/components/UtilRenderers/YtTextFormatterNew.vue @@ -10,6 +10,7 @@ export default { props: { textRuns: {}, + additionalInfo: {}, }, emits: ["play", "pause"], data: () => ({ @@ -17,22 +18,21 @@ export default { }), computed: { formatTitle() { - let tempContent = ""; let img = ""; if (this.textRuns.content && this.textRuns.commandRuns) { - tempContent = this.textRuns.content; - let arrayWithIndexes = []; + let tempContent = this.textRuns.content; let arrayWithReplaceParts = []; this.textRuns.commandRuns.forEach((commandRun) => { - arrayWithIndexes.push([commandRun.startIndex, commandRun.length]); //[textToReplace, urlFromEndpoint] + const textBeforeReplace = this.textRuns.content.substring( + commandRun.startIndex, + commandRun.startIndex + commandRun.length + ); + // console.log("isExistInArray "+ textBeforeReplace + ": " + isExistInArray); if (commandRun.onTap.innertubeCommand.commandMetadata) { arrayWithReplaceParts.push([ - this.textRuns.content.substring( - commandRun.startIndex, - commandRun.startIndex + commandRun.length - ), + textBeforeReplace, commandRun.onTap.innertubeCommand.commandMetadata .webCommandMetadata.url, ]); @@ -46,9 +46,31 @@ export default { ]); } }); - console.log(arrayWithIndexes); - console.log(arrayWithReplaceParts); + const duplicates = []; + arrayWithReplaceParts = arrayWithReplaceParts.filter((subArr) => { + const isFirstElementDuplicate = duplicates.includes(subArr[0]); + if (isFirstElementDuplicate) { + return false; + } else { + duplicates.push(subArr[0]); + return true; + } + }); + console.log(arrayWithReplaceParts); + for (let i = 0; i < arrayWithReplaceParts.length; i++) { + for (let j = i + 1; j < arrayWithReplaceParts.length; j++) { + if ( + JSON.stringify(arrayWithReplaceParts[i]) === + JSON.stringify(arrayWithReplaceParts[j]) + ) { + arrayWithReplaceParts.splice(j, 1); + j--; + } + } + } + + // Replacing urls in description arrayWithReplaceParts.forEach((text) => { if (text[1].indexOf("/hashtag/") > -1) { //skip @@ -62,8 +84,11 @@ export default { img + nameOfUrl + ""; - tempContent = tempContent.replace(text[0], newUrl); - } else if (text[1].indexOf("/channel/") > -1) { + tempContent = tempContent.replaceAll(text[0], newUrl); + } else if ( + text[1].indexOf("/channel/") > -1 || + text[1].indexOf("youtube.com/c/") > -1 + ) { let nameOfUrl = text[0].replace(/   /, " "); // let newUrl = // "' + img + nameOfUrl + ""; @@ -75,7 +100,7 @@ export default { img + nameOfUrl + ""; - tempContent = tempContent.replace(text[0], newUrl); + tempContent = tempContent.replaceAll(text[0], newUrl); } else { let params = new Proxy(new URLSearchParams(text[1]), { get: (searchParams, prop) => searchParams.get(prop), @@ -83,12 +108,13 @@ export default { let url = decodeURI(params.q); let newUrl = "' + text[0] + ""; - tempContent = tempContent.replace(text[0], newUrl); + tempContent = tempContent.replaceAll(text[0], newUrl); } }); + return tempContent; + } else { + return this.textRuns.content; } - console.log(tempContent); - return tempContent; }, }, created() { @@ -97,7 +123,6 @@ export default { }, methods: { openExternal(url) { - console.log(url); this.$vuetube.openExternal(url); }, async openInternal(url) { From 9068543a04c987801e3cbf5a10f22c2b16e984e1 Mon Sep 17 00:00:00 2001 From: Pixkk <30828435+pixkk@users.noreply.github.com> Date: Sun, 14 May 2023 18:54:42 +0300 Subject: [PATCH 4/5] Delete node.js.yml --- .github/workflows/node.js.yml | 115 ---------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 .github/workflows/node.js.yml diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index 9327cf8..0000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: CI - -on: - push: - branches: - - main - - dev - -env: - NODE_VERSION: 16 - -jobs: - build: - name: Build web assets - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm i; cd NUXT; npm i - - name: Set App Version - working-directory: NUXT - run: sed -i 's/dev-local/${{ github.sha }}/' nuxt.config.js - - name: Build web assets - working-directory: NUXT - run: npm run generate - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: dist - path: dist - android: - name: Build Android platform - runs-on: ubuntu-latest - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: dist - path: dist - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm i - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Copy web assets to native platform - run: npx cap copy android - - name: Update native platform - run: npx cap update android - - name: Build with Gradle - working-directory: android - run: chmod +x gradlew; ./gradlew clean assembleRelease -x test -Pandroid.injected.signing.store.file=/home/runner/work/VueTube/VueTube/android/key.jks -Pandroid.injected.signing.store.password=${{ secrets.ANDROID_STORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.ANDROID_KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.ANDROID_KEY_PASSWORD }} - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: android - path: android/app/build/outputs/apk/release/app-release.apk - - ios: - name: Build iOS platform - runs-on: macos-latest - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: dist - path: dist - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm i - - name: Copy web assets to native platform - run: npx cap copy ios - - name: Update native platform - run: npx cap update ios - - name: Add empty `GoogleService-Info.plist` - run: echo "$GOOGLE_SERVICE_INFO_PLIST" > ios/App/App/GoogleService-Info.plist - env: - GOOGLE_SERVICE_INFO_PLIST: ${{secrets.GOOGLE_SERVICE_INFO_PLIST}} - - name: Build and archive with xcodebuild - working-directory: ios - run: xcodebuild - -workspace App/App.xcworkspace - -scheme App - -archivePath App/build/App.xarchive - clean build archive - CODE_SIGN_IDENTITY="" - CODE_SIGNING_REQUIRED=NO - CODE_SIGNING_ALLOWED="NO" - CODE_SIGN_ENTITLEMENTS="" - - name: Make IPA - run: mkdir Payload && mv ~/Library/Developer/Xcode/DerivedData/App-*/Build/Products/Debug-iphoneos/App.app/ Payload && zip -r Payload.zip Payload && mv Payload.zip VueTube.ipa - - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: iOS - path: VueTube.ipa From cfe67eab0eea9fefd0ffee31faf0ff01478d7a63 Mon Sep 17 00:00:00 2001 From: Blank-1973 <50211074+Blank-1973@users.noreply.github.com> Date: Wed, 10 May 2023 18:45:35 +0900 Subject: [PATCH 5/5] Update japanese.js --- NUXT/plugins/languages/japanese.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/NUXT/plugins/languages/japanese.js b/NUXT/plugins/languages/japanese.js index d4b3c9a..d37bf46 100644 --- a/NUXT/plugins/languages/japanese.js +++ b/NUXT/plugins/languages/japanese.js @@ -55,7 +55,9 @@ module.exports = { language: "言語", backup: "バックアップ", backupinfo: "アプリの設定をバックアップ、または復元する。", - restore: "復元" + restore: "復元", + personalizedrecommendations: "Personalized recommendations", + personalizedrecommendationsinfo: "Receive personalized recommendations in exchange for sending watch time telemetry.", }, theme: { normal: "通常", @@ -73,6 +75,17 @@ module.exports = { roundthumbnails: "丸みを帯びたサムネイル", roundwatchpagecomponents: "視聴画面のコンポーネントを丸く", radius: "半径", + launchscreen: "起動画面", + centeredlayout: "中央揃えのレイアウト", + fullscreenlayout: "全画面レイアウト", + themedicon: "テーマアイコン", + bottomnavigation: "下部のナビゲーション", + shift: "シフト", + showlabels: "ラベルを表示する", + mdi: "MDI", + materialsymbols: "マテリアルシンボル", + fluentuiicons: "FluentUI アイコン", + ibmcarbonicons: "IBM カーボン アイコン", }, startup: { defaultpage: "起動時のページ",