diff --git a/fixChrome.cjs b/fixChrome.cjs deleted file mode 100644 index e68be20..0000000 --- a/fixChrome.cjs +++ /dev/null @@ -1,11 +0,0 @@ -console.log('fixChrome.js loaded'); - -const fs = require('fs') - -const manifest = require('./dist/manifest.json') -manifest.web_accessible_resources[0].resources.push('index.html') -manifest.action.default_popup = 'popup.html' -//写回文件 -fs.writeFileSync('./dist/manifest.json', JSON.stringify(manifest, null, 2)) - -console.log('fixChrome.js done'); diff --git a/fixFirefox.cjs b/fixFirefox.cjs deleted file mode 100644 index af9b823..0000000 --- a/fixFirefox.cjs +++ /dev/null @@ -1,22 +0,0 @@ -console.log('fixFirefox.js loaded'); - -const fs = require('fs') - -const manifest = require('./dist/manifest.json') -manifest.web_accessible_resources[0].resources.push('index.html') -manifest.action.default_popup = 'popup.html' -//browser_specific_settings -manifest.browser_specific_settings = { - "gecko": { - "id": "bilibili-subtitle@indiekky" - } -} -//background -manifest.background = { - type: "module", - scripts: [manifest.background.service_worker] -} -//写回文件 -fs.writeFileSync('./dist/manifest.json', JSON.stringify(manifest, null, 2)) - -console.log('fixFirefox.js done'); diff --git a/manifest.config.ts b/manifest.config.ts new file mode 100644 index 0000000..55c8394 --- /dev/null +++ b/manifest.config.ts @@ -0,0 +1,63 @@ +import {defineManifest} from '@crxjs/vite-plugin' +// @ts-ignore +import packageJson from './package.json' + +const {version} = packageJson + +// Convert from Semver (example: 0.1.0-beta6) +const [major, minor, patch, label = '0'] = version + // can only contain digits, dots, or dash + .replace(/[^\d.-]+/g, '') + // split into version parts + .split(/[.-]/) + +export default defineManifest(async (env) => ({ + "name": "哔哩哔哩字幕列表", + "description": "显示B站视频的字幕列表,可点击跳转与下载字幕,并支持翻译和总结字幕!", + "version": `${major}.${minor}.${patch}`, + "manifest_version": 3, + "permissions": [ + "storage", + ], + "host_permissions": [ + "http://localhost/*", + "http://127.0.0.1/*" + ], + "background": { + "service_worker": "src/chrome/background.ts", + "type": "module" + }, + "options_page": "options.html", + "content_scripts": [ + { + "matches": ["https://www.bilibili.com/video/*", "https://www.bilibili.com/list/*"], + "js": ["src/inject/inject.ts"] + } + ], + "icons": { + "16": "favicon-16x16.png", + "32": "favicon-32x32.png", + "48": "favicon-48x48.png", + "128": "favicon-128x128.png" + }, + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "favicon-16x16.png", + "32": "favicon-32x32.png", + "48": "favicon-48x48.png", + "128": "favicon-128x128.png" + } + }, + "web_accessible_resources": [ + { + "matches": [ + "https://www.bilibili.com/video/*", "https://www.bilibili.com/list/*" + ], + "resources": [ + "index.html", + ], + "use_dynamic_url": true + } + ] +})) diff --git a/manifest.json b/manifest.json deleted file mode 100644 index f2f73f3..0000000 --- a/manifest.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "哔哩哔哩字幕列表", - "description": "显示B站视频的字幕列表,可点击跳转与下载字幕,并支持翻译和总结字幕!", - "version": "1.10.6", - "manifest_version": 3, - "permissions": [ - "storage" - ], - "host_permissions": [ - "http://localhost/*", - "http://127.0.0.1/*" - ], - "background": { - "service_worker": "src/chrome/background.ts" - }, - "content_scripts": [ - { - "matches": ["https://www.bilibili.com/video/*", "https://www.bilibili.com/list/*"], - "js": ["src/chrome/content-script.cjs"] - } - ], - "icons": { - "16": "favicon-16x16.png", - "32": "favicon-32x32.png", - "48": "favicon-48x48.png", - "128": "favicon-128x128.png" - }, - "action": { - "default_popup": "index.html", - "default_icon": { - "16": "favicon-16x16.png", - "32": "favicon-32x32.png", - "48": "favicon-48x48.png", - "128": "favicon-128x128.png" - } - } -} diff --git a/package.json b/package.json index da832ad..749ab0b 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "main": "index.js", "scripts": { "dev": "vite", - "build_chrome": "tsc && vite build -m production_chrome && node fixChrome.cjs", - "build_firefox": "tsc && vite build -m production_chrome && node fixFirefox.cjs", + "build": "tsc && vite build", "fix": "eslint --fix --quiet ." }, "author": "IndieKKY", @@ -47,6 +46,7 @@ "devDependencies": { "@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/typography": "^0.5.8", + "@types/node": "^20.8.10", "@types/chrome": "^0.0.203", "@types/js-search": "^1.4.0", "@types/lodash-es": "^4.17.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b4162e..1594e9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: '@types/lodash-es': specifier: ^4.17.6 version: 4.17.6 + '@types/node': + specifier: ^20.8.10 + version: 20.16.10 '@types/pako': specifier: ^2.0.0 version: 2.0.0 @@ -490,6 +493,9 @@ packages: '@types/ms@0.7.31': resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + '@types/node@20.16.10': + resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} + '@types/pako@2.0.0': resolution: {integrity: sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==} @@ -2343,6 +2349,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} @@ -2881,6 +2890,10 @@ snapshots: '@types/ms@0.7.31': {} + '@types/node@20.16.10': + dependencies: + undici-types: 6.19.8 + '@types/pako@2.0.0': {} '@types/prop-types@15.7.5': {} @@ -4955,6 +4968,8 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + undici-types@6.19.8: {} + unified@10.1.2: dependencies: '@types/unist': 2.0.6 diff --git a/public/popup.html b/popup.html similarity index 100% rename from public/popup.html rename to popup.html diff --git a/public/bibigpt.png b/public/bibigpt.png deleted file mode 100644 index 5be4212..0000000 Binary files a/public/bibigpt.png and /dev/null differ diff --git a/public/immersive-summary.png b/public/immersive-summary.png deleted file mode 100644 index 59c94fb..0000000 Binary files a/public/immersive-summary.png and /dev/null differ diff --git a/public/my-article-summarizer.png b/public/my-article-summarizer.png deleted file mode 100644 index 12cfd8b..0000000 Binary files a/public/my-article-summarizer.png and /dev/null differ diff --git a/public/openai-up.ico b/public/openai-up.ico deleted file mode 100644 index fbcfb14..0000000 Binary files a/public/openai-up.ico and /dev/null differ diff --git a/push.sh b/push.sh index ec5f47f..9b2a2e4 100755 --- a/push.sh +++ b/push.sh @@ -17,13 +17,11 @@ done # Update the version in package.json sed -i '' "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/g" package.json -# Update the version in manifest.json -sed -i '' "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/g" manifest.json echo "Version updated to: $new_version" # build -pnpm run build_chrome +pnpm run build # zip dist rm -f dist.zip cd dist diff --git a/src/App.tsx b/src/App.tsx index af6d03d..3487547 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -75,7 +75,7 @@ function App() { useTranslateService() useSearchService() - return
diff --git a/src/chrome/content-script.cjs b/src/inject/inject.ts similarity index 84% rename from src/chrome/content-script.cjs rename to src/inject/inject.ts index d478fe3..4579366 100644 --- a/src/chrome/content-script.cjs +++ b/src/inject/inject.ts @@ -1,9 +1,9 @@ -const {TOTAL_HEIGHT_DEF, HEADER_HEIGHT, TOTAL_HEIGHT_MIN, TOTAL_HEIGHT_MAX, IFRAME_ID} = require("../const"); -var totalHeight = TOTAL_HEIGHT_DEF +import {TOTAL_HEIGHT_DEF, HEADER_HEIGHT, TOTAL_HEIGHT_MIN, TOTAL_HEIGHT_MAX, IFRAME_ID} from '@/const' +let totalHeight = TOTAL_HEIGHT_DEF const getVideoElement = () => { const videoWrapper = document.getElementById('bilibili-player') - return videoWrapper.querySelector('video') + return videoWrapper?.querySelector('video') as HTMLVideoElement | undefined } const timerIframe = setInterval(function () { @@ -24,7 +24,10 @@ const timerIframe = setInterval(function () { const iframe = document.createElement('iframe') iframe.id = IFRAME_ID iframe.src = chrome.runtime.getURL('index.html') - iframe.style = 'border: none; width: 100%; height: 44px;margin-bottom: 3px;' + iframe.style.border = 'none' + iframe.style.width = '100%' + iframe.style.height = '44px' + iframe.style.marginBottom = '3px' iframe.allow = 'clipboard-read; clipboard-write;' if (vKey) { iframe.dataset[vKey] = danmukuBox?.dataset[vKey] @@ -37,19 +40,19 @@ const timerIframe = setInterval(function () { } }, 1000) -let aid = 0 +let aid: number | null = null let title = '' -let pages = [] -let pagesMap = {} +let pages: any[] = [] +let pagesMap: Record = {} -let lastAidOrBvid = null +let lastAidOrBvid: string | null = null const refreshVideoInfo = async () => { - const iframe = document.getElementById(IFRAME_ID) + const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined if (!iframe) return // fix: https://github.com/IndieKKY/bilibili-subtitle/issues/5 // 处理稍后再看的url( https://www.bilibili.com/list/watchlater?bvid=xxx&oid=xxx ) - const pathSearchs = {} + const pathSearchs: Record = {} location.search.slice(1).replace(/([^=&]*)=([^=&]*)/g, (matchs, a, b, c) => pathSearchs[a] = b) // bvid @@ -72,7 +75,7 @@ const refreshVideoInfo = async () => { let cid let subtitles if (aidOrBvid.toLowerCase().startsWith('av')) {//avxxx - aid = aidOrBvid.slice(2) + aid = parseInt(aidOrBvid.slice(2)) pages = await fetch(`https://api.bilibili.com/x/player/pagelist?aid=${aid}`, {credentials: 'include'}).then(res => res.json()).then(res => res.data) cid = pages[0].cid title = pages[0].part @@ -100,7 +103,7 @@ const refreshVideoInfo = async () => { console.debug('refreshVideoInfo: ', aid, cid, pages, subtitles) //send setVideoInfo - iframe.contentWindow.postMessage({ + iframe.contentWindow?.postMessage({ type: 'setVideoInfo', url: location.origin + location.pathname, title, @@ -112,10 +115,10 @@ const refreshVideoInfo = async () => { } } -let lastAid = null -let lastCid = null +let lastAid: number | null = null +let lastCid: number | null = null const refreshSubtitles = () => { - const iframe = document.getElementById(IFRAME_ID) + const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined if (!iframe) return const urlSearchParams = new URLSearchParams(window.location.search) @@ -136,7 +139,7 @@ const refreshSubtitles = () => { .then(res => res.json()) .then(res => { // console.log('refreshSubtitles: ', aid, cid, res) - iframe.contentWindow.postMessage({ + iframe.contentWindow?.postMessage({ type: 'setInfos', infos: res.data.subtitle.subtitles }, '*') @@ -150,8 +153,10 @@ window.addEventListener("message", (event) => { const {data} = event if (data.type === 'fold') { - const iframe = document.getElementById(IFRAME_ID) - iframe.style.height = (data.fold ? HEADER_HEIGHT : totalHeight) + 'px' + const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined + if (iframe) { + iframe.style.height = (data.fold ? HEADER_HEIGHT : totalHeight) + 'px' + } } if (data.type === 'move') { @@ -178,7 +183,7 @@ window.addEventListener("message", (event) => { url = url.replace('http://', 'https://') } fetch(url).then(res => res.json()).then(res => { - event.source.postMessage({ + event.source?.postMessage({ data: { info: data.info, data: res, @@ -190,7 +195,7 @@ window.addEventListener("message", (event) => { if (data.type === 'getCurrentTime') { const video = getVideoElement() if (video) { - event.source.postMessage({ + event.source?.postMessage({ data: { currentTime: video.currentTime }, type: 'setCurrentTime' @@ -201,7 +206,7 @@ window.addEventListener("message", (event) => { if (data.type === 'getSettings') { const videoElement = getVideoElement() totalHeight = videoElement ? Math.min(Math.max(videoElement.offsetHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX) : TOTAL_HEIGHT_DEF - event.source.postMessage({ + event.source?.postMessage({ data: { noVideo: !videoElement, totalHeight, @@ -211,7 +216,7 @@ window.addEventListener("message", (event) => { if (data.type === 'downloadAudio') { const html = document.getElementsByTagName('html')[0].innerHTML - const playInfo = JSON.parse(html.match(/window.__playinfo__=(.+?)<\/script/)[1]) + const playInfo = JSON.parse(html.match(/window.__playinfo__=(.+?)<\/script/)?.[1] ?? '{}') const audioUrl = playInfo.data.dash.audio[0].baseUrl fetch(audioUrl).then(res => res.blob()).then(blob => { diff --git a/src/redux/envReducer.ts b/src/redux/envReducer.ts index d912939..e7ca207 100644 --- a/src/redux/envReducer.ts +++ b/src/redux/envReducer.ts @@ -67,11 +67,11 @@ const initialState: EnvState = { }, totalHeight: TOTAL_HEIGHT_DEF, autoScroll: true, - currentTime: import.meta.env.VITE_ENV === 'web-dev' ? 30 : undefined, + // currentTime: import.meta.env.VITE_ENV === 'web-dev' ? 30 : undefined, envReady: false, tempReady: false, fold: true, - data: import.meta.env.VITE_ENV === 'web-dev' ? getDevData() : undefined, + // data: import.meta.env.VITE_ENV === 'web-dev' ? getDevData() : undefined, transResults: {}, inputting: false, diff --git a/tsconfig.json b/tsconfig.json index 9539005..d7dd2fb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,10 @@ "jsx": "react-jsx", "types": [ "@types/chrome" - ] + ], + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/tsconfig.node.json b/tsconfig.node.json index e229c97..be6f5d9 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -6,5 +6,5 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true }, - "include": ["vite.config.ts", "manifest.json"] + "include": ["vite.config.ts", "manifest.config.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 70c859f..fb59aa4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,24 +2,33 @@ import {defineConfig, PluginOption} from 'vite' import react from '@vitejs/plugin-react' import {visualizer} from "rollup-plugin-visualizer"; import {crx} from '@crxjs/vite-plugin' +import path from "path" // @ts-ignore import manifest from './manifest.json' // https://vitejs.dev/config/ export default ({mode}) => { - const plugins = [ - react(), - visualizer() as PluginOption, - ] - // @ts-ignore - if (mode === 'production_chrome' || mode === 'production_edge') { - plugins.push(crx({ - manifest, - })) - } return defineConfig({ base: '/', - plugins, + build: { + rollupOptions: { + input: { + index: 'index.html', + }, + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + } + }, + plugins: [ + react(), + crx({ + manifest, + }), + visualizer() as PluginOption, + ], css: { modules: { localsConvention: "camelCase" diff --git a/vite.config.ts.timestamp-1705728659971.mjs b/vite.config.ts.timestamp-1705728659971.mjs deleted file mode 100644 index d2c3687..0000000 --- a/vite.config.ts.timestamp-1705728659971.mjs +++ /dev/null @@ -1,66 +0,0 @@ -// vite.config.ts -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import { visualizer } from "rollup-plugin-visualizer"; -import { crx } from "@crxjs/vite-plugin"; - -// manifest.json -var manifest_default = { - name: "\u54D4\u54E9\u54D4\u54E9\u5B57\u5E55\u5217\u8868", - description: "\u663E\u793AB\u7AD9\u89C6\u9891\u7684\u5B57\u5E55\u5217\u8868,\u53EF\u70B9\u51FB\u8DF3\u8F6C\u4E0E\u4E0B\u8F7D\u5B57\u5E55,\u5E76\u652F\u6301\u7FFB\u8BD1\u548C\u603B\u7ED3\u5B57\u5E55!", - version: "1.7.11", - manifest_version: 3, - permissions: [ - "storage" - ], - background: { - service_worker: "src/chrome/background.ts" - }, - content_scripts: [ - { - matches: ["https://www.bilibili.com/video/*", "https://www.bilibili.com/list/*"], - js: ["src/chrome/content-script.cjs"] - } - ], - icons: { - "16": "favicon-16x16.png", - "32": "favicon-32x32.png", - "48": "favicon-48x48.png", - "128": "favicon-128x128.png" - }, - action: { - default_popup: "index.html", - default_icon: { - "16": "favicon-16x16.png", - "32": "favicon-32x32.png", - "48": "favicon-48x48.png", - "128": "favicon-128x128.png" - } - } -}; - -// vite.config.ts -var vite_config_default = ({ mode }) => { - const plugins = [ - react(), - visualizer() - ]; - if (mode === "production_chrome") { - plugins.push(crx({ - manifest: manifest_default - })); - } - return defineConfig({ - base: "/", - plugins, - css: { - modules: { - localsConvention: "camelCase" - } - } - }); -}; -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvZmVuZ3l1ZXhpYW5nL2RhdGEvcHJvamVjdC9iaWxpYmlsaS1zdWJ0aXRsZVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL2Zlbmd5dWV4aWFuZy9kYXRhL3Byb2plY3QvYmlsaWJpbGktc3VidGl0bGUvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL2Zlbmd5dWV4aWFuZy9kYXRhL3Byb2plY3QvYmlsaWJpbGktc3VidGl0bGUvdml0ZS5jb25maWcudHNcIjtpbXBvcnQge2RlZmluZUNvbmZpZywgUGx1Z2luT3B0aW9ufSBmcm9tICd2aXRlJ1xuaW1wb3J0IHJlYWN0IGZyb20gJ0B2aXRlanMvcGx1Z2luLXJlYWN0J1xuaW1wb3J0IHt2aXN1YWxpemVyfSBmcm9tIFwicm9sbHVwLXBsdWdpbi12aXN1YWxpemVyXCI7XG5pbXBvcnQge2NyeH0gZnJvbSAnQGNyeGpzL3ZpdGUtcGx1Z2luJ1xuLy8gQHRzLWlnbm9yZVxuaW1wb3J0IG1hbmlmZXN0IGZyb20gJy4vbWFuaWZlc3QuanNvbidcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0ICh7bW9kZX0pID0+IHtcbiAgY29uc3QgcGx1Z2lucyA9IFtcbiAgICByZWFjdCgpLFxuICAgIHZpc3VhbGl6ZXIoKSBhcyBQbHVnaW5PcHRpb24sXG4gIF1cbiAgLy8gQHRzLWlnbm9yZVxuICBpZiAobW9kZSA9PT0gJ3Byb2R1Y3Rpb25fY2hyb21lJykge1xuICAgIHBsdWdpbnMucHVzaChjcngoe1xuICAgICAgbWFuaWZlc3QsXG4gICAgfSkpXG4gIH1cbiAgcmV0dXJuIGRlZmluZUNvbmZpZyh7XG4gICAgYmFzZTogJy8nLFxuICAgIHBsdWdpbnMsXG4gICAgY3NzOiB7XG4gICAgICBtb2R1bGVzOiB7XG4gICAgICAgIGxvY2Fsc0NvbnZlbnRpb246IFwiY2FtZWxDYXNlXCJcbiAgICAgIH1cbiAgICB9XG4gIH0pXG59XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQXdVLFNBQVEsb0JBQWlDO0FBQ2pYLE9BQU8sV0FBVztBQUNsQixTQUFRLGtCQUFpQjtBQUN6QixTQUFRLFdBQVU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBS2xCLElBQU8sc0JBQVEsQ0FBQyxFQUFDLEtBQUksTUFBTTtBQUN6QixRQUFNLFVBQVU7QUFBQSxJQUNkLE1BQU07QUFBQSxJQUNOLFdBQVc7QUFBQSxFQUNiO0FBRUEsTUFBSSxTQUFTLHFCQUFxQjtBQUNoQyxZQUFRLEtBQUssSUFBSTtBQUFBLE1BQ2Y7QUFBQSxJQUNGLENBQUMsQ0FBQztBQUFBLEVBQ0o7QUFDQSxTQUFPLGFBQWE7QUFBQSxJQUNsQixNQUFNO0FBQUEsSUFDTjtBQUFBLElBQ0EsS0FBSztBQUFBLE1BQ0gsU0FBUztBQUFBLFFBQ1Asa0JBQWtCO0FBQUEsTUFDcEI7QUFBQSxJQUNGO0FBQUEsRUFDRixDQUFDO0FBQ0g7IiwKICAibmFtZXMiOiBbXQp9Cg==