diff --git a/layout/default/layout.css b/layout/default/layout.css index 93fe80fd8..45990d3cb 100644 --- a/layout/default/layout.css +++ b/layout/default/layout.css @@ -5571,4 +5571,21 @@ div[data-decoration="bronze"]::after{ /* 火狐隐藏滚动条 */ * { scrollbar-width: none; -} \ No newline at end of file +} +/* 更新进度条 */ +progress.progress { + width: 75%; + height: 10px; + border: 2px solid; + border-radius: 15px; + vertical-align: middle; + -webkit-appearance: none; +} +progress.progress::-webkit-progress-bar { + background: rgb(239, 239, 239); + border-radius: 0.2rem; +} +progress.progress::-webkit-progress-value { + border-radius: 0.2rem; + background: rgb(0, 117, 255); +} diff --git a/node_modules/noname-typings/type.d.ts b/node_modules/noname-typings/type.d.ts index 0dca19485..31c7f6dee 100644 --- a/node_modules/noname-typings/type.d.ts +++ b/node_modules/noname-typings/type.d.ts @@ -73,7 +73,6 @@ declare type Dialog = import('../../noname/library/index.js').Dialog; declare type GameEvent = import('../../noname/library/index.js').GameEvent; declare type GameEventPromise = import('../../noname/library/index.js').GameEventPromise; declare type Player = import('../../noname/library/index.js').Player; -declare type VCard = import('../../noname/library/index.js').VCard; declare type Control = import('../../noname/library/index.js').Control; declare type Video = import('../../noname/game/index.js').Video; @@ -85,6 +84,27 @@ declare type Sex = 'male' | 'female' | 'dobule' | 'none'; declare type Character = [Sex, string, number | string, string[], string[]] | [Sex, string, number | string, string[]]; declare type Select = [number, number]; +declare interface progress extends HTMLDivElement { + /** 获取标题 */ + getTitle: () => string; + /** 更改标题 */ + setTitle: (title: string) => void; + /** 获取显示的文件名 */ + getFileName: () => string; + /** 更改显示的文件名 */ + setFileName: (title: string) => void; + /** 获取进度*/ + getProgressValue: () => number; + /** 更改进度*/ + setProgressValue: (value: number) => void; + /** 获取下载文件总数 */ + getProgressMax: () => number; + /** 修改下载文件总数 */ + setProgressMax: (max: number) => void; + /** 通过数组自动解析文件名 */ + autoSetFileNameFromArray: (fileNameList: string[]) => void; +} + /** * 导入武将包的配置 */ @@ -269,7 +289,7 @@ declare interface importModeConfig { */ characterSort?: SMap>; /** 卡牌(主要是放些该模式下特有的卡牌) */ - card?: SMap; + card?: SMap; /** * 卡包 */ diff --git a/noname/init/index.js b/noname/init/index.js index 16863f2a1..2d442e9f7 100644 --- a/noname/init/index.js +++ b/noname/init/index.js @@ -19,7 +19,7 @@ export function canUseHttpProtocol() { // 如果是http了就不用 if (location.protocol.startsWith('http')) return false; // 首次启动不更新(即还没进行过新手教程) - if (!lib.config.new_tutorial) return false; + if (!config.get('new_tutorial')) return false; if (typeof nonameInitialized == 'string') { // 手机端 if (window.cordova) { @@ -134,18 +134,6 @@ export async function boot() { setWindowListener(); const promiseErrorHandler = await setOnError(); - // 无名杀更新日志 - if (window.noname_update) { - Reflect.set(lib, 'version', window.noname_update.version); - lib.changeLog = window.noname_update.changeLog; - if (window.noname_update.players) { - lib.changeLog.push('players://' + JSON.stringify(window.noname_update.players)); - } - if (window.noname_update.cards) { - lib.changeLog.push('cards://' + JSON.stringify(window.noname_update.cards)); - } - delete window.noname_update; - } // 确认手机端平台 const noname_inited = localStorage.getItem('noname_inited'); if (noname_inited && noname_inited !== 'nodejs') { @@ -498,6 +486,39 @@ export async function boot() { } delete _status.htmlbg; + // 无名杀更新日志 + if (window.noname_update) { + Reflect.set(lib, 'version', window.noname_update.version); + // 更全面的更新内容 + if (config.get(`version_description_v${window.noname_update.version}`)) { + try { + const description = config.get(`version_description_v${window.noname_update.version}`); + const html = String.raw; + lib.changeLog.push( + html` +
+ ${description.author.login}于${description.published_at}发布 + `.trim(), + description.body.replaceAll('\n', '
') + ); + } catch (e) { + console.error(e); + lib.changeLog.push(...window.noname_update.changeLog); + } + } + // 原更新内容 + else { + lib.changeLog.push(...window.noname_update.changeLog); + } + if (window.noname_update.players) { + lib.changeLog.push('players://' + JSON.stringify(window.noname_update.players)); + } + if (window.noname_update.cards) { + lib.changeLog.push('cards://' + JSON.stringify(window.noname_update.cards)); + } + delete window.noname_update; + } + // 虽然但是,我就暴露个import,应该没啥问题 Reflect.set(window, 'game', { import: game.import.bind(null) @@ -715,6 +736,7 @@ async function loadConfig() { Reflect.set(lib, 'config', Reflect.get(window, 'config')); Reflect.set(lib, 'configOL', {}); Reflect.deleteProperty(window, 'config'); + let result; if (localStorage.getItem(`${lib.configprefix}nodb`)) Reflect.set(window, 'nodb', true); diff --git a/noname/library/update.js b/noname/library/update.js new file mode 100644 index 000000000..a74a896cc --- /dev/null +++ b/noname/library/update.js @@ -0,0 +1,418 @@ +import { ui } from '../../noname.js'; + +// https://github.com/libccy/noname/archive/refs/tags/v1.10.10.zip + +/** + * HTTP响应头中的Rate Limit相关信息: + * X-RateLimit-Limit: 请求总量限制 + * X-RateLimit-Remaining: 剩余请求次数 + * X-RateLimit-Reset: 限制重置时间(UTC时间戳) +*/ + +/** @type { HeadersInit } */ +const defaultHeaders = { + 'Accept': 'application/vnd.github.v3+json', + // 根据GitHub API的要求添加适当的认证头信息 + // 如果公共仓库则无需认证,私有仓库需提供token + // 'Authorization': `Bearer ${YOUR_GITHUB_PERSONAL_ACCESS_TOKEN}` +}; + +const defaultResponse = response => { + const limit = response.headers.get("X-RateLimit-Limit"); + const remaining = response.headers.get("X-RateLimit-Remaining"); + const reset = response.headers.get("X-RateLimit-Reset"); + console.log(`请求总量限制`, limit); + console.log(`剩余请求次数`, remaining); + console.log(`限制重置时间`, (new Date(reset * 1000)).toLocaleString()); +}; + +/** + * 字节转换 + * @param { number } limit + */ +export function parseSize(limit) { + let size = ""; + if (limit < 1 * 1024) { + // 小于1KB,则转化成B + size = limit.toFixed(2) + "B" + } else if (limit < 1 * 1024 * 1024) { + // 小于1MB,则转化成KB + size = (limit / 1024).toFixed(2) + "KB" + } else if (limit < 1 * 1024 * 1024 * 1024) { + // 小于1GB,则转化成MB + size = (limit / (1024 * 1024)).toFixed(2) + "MB" + } else { + // 其他转化成GB + size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB" + } + + // 转成字符串 + let sizeStr = size + ""; + // 获取小数点处的索引 + let index = sizeStr.indexOf("."); + // 获取小数点后两位的值 + let dou = sizeStr.slice(index + 1, 2); + // 判断后两位是否为00,如果是则删除00 + if (dou == "00") { + return sizeStr.slice(0, index) + sizeStr.slice(index + 3, 2); + } + return size; +}; + +/** + * 对比版本号 + * @param { string } ver1 + * @param { string } ver2 + * @returns { -1 | 0 | 1 } + */ +export function checkVersion(ver1, ver2) { + if (typeof ver1 !== 'string') ver1 = String(ver1); + if (typeof ver2 !== 'string') ver2 = String(ver2); + + // 移除 'v' 开头 + if (ver1.startsWith('v')) ver1 = ver1.slice(1); + if (ver2.startsWith('v')) ver2 = ver2.slice(1); + + // 验证版本号格式 + if (/[^0-9.-]/i.test(ver1) || /[^0-9.-]/i.test(ver2)) { + throw new Error('Invalid characters found in the version numbers'); + } + + /** @param { string } str */ + function* walk(str) { + let part = ''; + for (const char of str) { + if (char === '.' || char === '-') { + if (part) yield Number(part); + part = ''; + } else { + part += char; + } + } + if (part) yield Number(part); + } + + const iterator1 = walk(ver1); + const iterator2 = walk(ver2); + + while (true) { + const iter1 = iterator1.next(); + const iter2 = iterator2.next(); + let { value: item1 } = iter1; + let { value: item2 } = iter2; + + // 如果任意一个迭代器已经没有剩余值,将该值视为0 + item1 = item1 === undefined ? 0 : item1; + item2 = item2 === undefined ? 0 : item2; + + if (isNaN(item1) || isNaN(item2)) { + throw new Error('Non-numeric part found in the version numbers'); + } else if (item1 > item2) { + return 1; + } else if (item1 < item2) { + return -1; + } else { + if (iter1.done && iter2.done) break; + } + } + + // 若正常遍历结束,说明版本号相等 + return 0; +}; + +/** + * + * 获取指定仓库的tags + * @param { Object } options + * @param { string } [options.username = 'libccy'] 仓库拥有者 + * @param { string } [options.repository = 'noname'] 仓库名称 + * @param { string } [options.accessToken] 身份令牌 + * @returns { Promise<{ commit: { sha: string, url: string }, name: string, node_id: string, tarball_url: string, zipball_url: string }[]> } + * + * @example + * ```js + * getRepoTags().then(tags => { + * console.log("All tags:", tags.map(tag => tag.name)); + * // 获取最新tag(假设按时间顺序排列,最新tag在数组首位) + * const latestTag = tags[0].name; + * console.log("Latest tag:", latestTag); + * }); + * ``` + */ +export async function getRepoTags(options = { username: 'libccy', repository: 'noname' }) { + const { username = 'libccy', repository = 'noname', accessToken } = options; + const headers = Object.assign({}, defaultHeaders); + if (accessToken) { + headers['Authorization'] = `token ${accessToken}`; + } + const url = `https://api.github.com/repos/${username}/${repository}/tags`; + const response = await fetch(url, { headers }); + defaultResponse(response); + if (response.ok) { + const data = await response.json(); + return data; + } else { + throw new Error(`Error fetching tags: ${response.statusText}`); + } +}; + +/** + * 获取指定仓库的指定tags的描述 + * @param { string } tagName tag名称 + * @param { Object } options + * @param { string } [options.username = 'libccy'] 仓库拥有者 + * @param { string } [options.repository = 'noname'] 仓库名称 + * @param { string } [options.accessToken] 身份令牌 + * @example + * ```js + * getRepoTagDescription('v1.10.10') + * .then(description => console.log(description)) + * .catch(error => console.error('Failed to fetch description:', error)); + * ``` + */ + +export async function getRepoTagDescription(tagName, options = { username: 'libccy', repository: 'noname' }) { + const { username = 'libccy', repository = 'noname', accessToken } = options; + const headers = Object.assign({}, defaultHeaders); + if (accessToken) { + headers['Authorization'] = `token ${accessToken}`; + } + const apiUrl = `https://api.github.com/repos/${username}/${repository}/releases/tags/${tagName}`; + const response = await fetch(apiUrl, { headers }); + defaultResponse(response); + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + const releaseData = await response.json(); + // console.log(releaseData); + // 从json里拿我们需要的 + return { + /** @type { { browser_download_url: string, content_type: string, name: string, size: number }[] } tag额外上传的素材包 */ + assets: releaseData.assets, + author: { + /** @type { string } 用户名 */ + login: releaseData.author.login, + /** @type { string } 用户头像地址 */ + avatar_url: releaseData.author.avatar_url, + /** @type { string } 用户仓库地址 */ + html_url: releaseData.author.html_url, + }, + /** @type { string } tag描述 */ + body: releaseData.body, + // created_at: (new Date(releaseData.created_at)).toLocaleString(), + /** @type { string } tag页面 */ + html_url: releaseData.html_url, + /** @type { string } tag名称 */ + name: releaseData.name, + /** 发布日期 */ + published_at: (new Date(releaseData.published_at)).toLocaleString(), + /** @type { string } 下载地址 */ + zipball_url: releaseData.zipball_url, + }; +}; + +/** + * + * 获取仓库指定分支和指定目录内的所有文件和目录 + * @param { string } [path = ''] 路径名称(可放参数) + * @param { string } [branch = ''] 仓库分支名称 + * @param { Object } options + * @param { string } [options.username = 'libccy'] 仓库拥有者 + * @param { string } [options.repository = 'noname'] 仓库名称 + * @param { string } [options.accessToken] 身份令牌 + * @returns { Promise<{ download_url: string, name: string, path: string, sha: string, size: number, type: 'file' } | { download_url: null, name: string, path: string, sha: string, size: 0, type: 'dir' }> } + * @example + * ```js + * getRepoFilesList() + * .then(files => console.log(files)) + * .catch(error => console.error('Failed to fetch files:', error)); + * ``` + */ +export async function getRepoFilesList(path = '', branch, options = { username: 'libccy', repository: 'noname' }) { + const { username = 'libccy', repository = 'noname', accessToken } = options; + const headers = Object.assign({}, defaultHeaders); + if (accessToken) { + headers['Authorization'] = `token ${accessToken}`; + } + let url = `https://api.github.com/repos/${username}/${repository}/contents/${path}`; + if (typeof branch == 'string' && branch.length > 0) { + const searchParams = new URLSearchParams(new URL(url).search.slice(1)); + if (searchParams.has('ref')) { + throw new TypeError(`设置了branch参数后,不应在path参数内拼接ref`); + } + searchParams.append('ref', branch); + url = searchParams.toString(); + } + const response = await fetch(url, { headers }); + defaultResponse(response); + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + const data = await response.json(); + console.log(data); + // 处理响应数据,返回文件列表 + return data.map(({ download_url, name, path, sha, size, type }) => ({ + download_url, + name, + path, + sha, + size, + type + })); +}; + +/** + * 请求一个文件而不是直接储存为文件 + * @param { string } url + * @param { (receivedBytes: number, total?:number, filename?: string) => void } [onProgress] + * @example + * ```js + * await getRepoTagDescription('v1.10.10').then(({ zipball_url }) => request(zipball_url)); + * ``` + */ +export async function request(url, onProgress) { + const response = await fetch(url, { + // 告诉服务器我们期望得到范围请求的支持 + headers: { 'Range': 'bytes=0-' }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // @ts-ignore + let total = parseInt(response.headers.get('Content-Length'), 10); + // 如果服务器未返回Content-Length,则无法准确计算进度 + // @ts-ignore + if (isNaN(total)) total = null; + // @ts-ignore + const reader = response.body.getReader(); + let filename; + try { + // @ts-ignore + filename = response.headers.get('Content-Disposition').split(';')[1].split('=')[1]; + } catch {} + let receivedBytes = 0; + let chunks = []; + + while (true) { + // 使用ReadableStream来获取部分数据并计算进度 + const { done, value } = await reader.read(); + + if (done) { + break; + } + + chunks.push(value); + receivedBytes += value.length; + + if (typeof onProgress == 'function') { + if (total) { + const progress = (receivedBytes / total) * 100; + onProgress(receivedBytes, progress, filename); + } else { + onProgress(receivedBytes, void 0, filename); + } + } + } + + // 合并chunks并转换为Blob + const blob = new Blob(chunks); + + // 仅做演示,打印已合并的Blob大小 + console.log(`Download completed. Total size: ${ parseSize(blob.size) }.`); + + return blob; +}; + +/** + * + * @param { string } [title] + * @param { string | number } [max] + * @param { string } [fileName] + * @param { string | number } [value] + * @returns { progress } + */ +export function createProgress(title, max, fileName, value) { + /** @type { progress } */ + // @ts-ignore + const parent = ui.create.div(ui.window, { + textAlign: 'center', + width: '300px', + height: '150px', + left: 'calc(50% - 150px)', + top: 'auto', + bottom: 'calc(50% - 75px)', + zIndex: '10', + boxShadow: 'rgb(0 0 0 / 40 %) 0 0 0 1px, rgb(0 0 0 / 20 %) 0 3px 10px', + backgroundImage: 'linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4))', + borderRadius: '8px', + overflow: 'hidden scroll' + }); + + // 可拖动 + parent.className = 'dialog'; + + const container = ui.create.div(parent, { + position: 'absolute', + top: '0', + left: '0', + width: '100%', + height: '100%' + }); + + container.ontouchstart = ui.click.dialogtouchStart; + container.ontouchmove = ui.click.touchScroll; + // @ts-ignore + container.style.WebkitOverflowScrolling = 'touch'; + parent.ontouchstart = ui.click.dragtouchdialog; + + const caption = ui.create.div(container, '', title, { + position: 'relative', + paddingTop: '8px', + fontSize: '20px' + }); + + ui.create.node('br', container); + + const tip = ui.create.div(container, { + position: 'relative', + paddingTop: '8px', + fontSize: '20px', + width: '100%' + }); + + const file = ui.create.node('span', tip, '', fileName); + file.style.width = file.style.maxWidth = '100%'; + ui.create.node('br', tip); + const index = ui.create.node('span', tip, '', String(value || '0')); + ui.create.node('span', tip, '', '/'); + const maxSpan = ui.create.node('span', tip, '', String(max || '未知')); + + ui.create.node('br', container); + + const progress = ui.create.node('progress.progress', container); + progress.setAttribute('value', value || '0'); + progress.setAttribute('max', max); + + parent.getTitle = () => caption.innerText; + parent.setTitle = title => caption.innerHTML = title; + parent.getFileName = () => file.innerText; + parent.setFileName = name => file.innerHTML = name; + parent.getProgressValue = () => progress.value; + parent.setProgressValue = value => progress.value = index.innerHTML = value; + parent.getProgressMax = () => progress.max; + parent.setProgressMax = max => progress.max = maxSpan.innerHTML = max; + parent.autoSetFileNameFromArray = fileNameList => { + if (fileNameList.length > 2) { + parent.setFileName(fileNameList.slice(0, 2).concat(`......等${fileNameList.length - 2}个文件`).join('
')); + } else if (fileNameList.length == 2) { + parent.setFileName(fileNameList.join('
')); + } else if (fileNameList.length == 1) { + parent.setFileName(fileNameList[0]); + } else { + parent.setFileName('当前没有正在下载的文件'); + } + }; + return parent; +}; \ No newline at end of file diff --git a/noname/ui/create/menu/pages/otherMenu.js b/noname/ui/create/menu/pages/otherMenu.js index 20896b145..f2e0951e6 100644 --- a/noname/ui/create/menu/pages/otherMenu.js +++ b/noname/ui/create/menu/pages/otherMenu.js @@ -15,8 +15,17 @@ import { clickMenuItem, createMenu, createConfig -} from "../index.js"; -import { ui, game, get ,ai ,lib, _status } from "../../../../../noname.js"; +} from '../index.js'; +import { ui, game, get, ai, lib, _status } from "../../../../../noname.js"; +import { + parseSize, + checkVersion, + getRepoTags, + getRepoTagDescription, + getRepoFilesList, + request, + createProgress +} from "../../../../library/update.js" export const otherMenu = function (connectMenu) { if (connectMenu) return; @@ -108,263 +117,229 @@ export const otherMenu = function (connectMenu) { var li1 = document.createElement('li'); var li2 = document.createElement('li'); var li3 = document.createElement('li'); - const trimURL = url => { - const updateURLS = lib.updateURLS; - for (const key in updateURLS) { - const updateURL = updateURLS[key]; - if (url == updateURL) return lib.configMenu.general.config.update_link.item[key]; - } - let index = url.indexOf('://'); - if (index != -1) url = url.slice(index + 3); - index = url.indexOf('/'); - if (index != -1) url = url.slice(0, index); - if (url.length > 15) { - const list = url.split('.'); - if (list.length > 1) list.shift(); - url = list.join('.'); - } - if (url.length > 15) { - const list = url.split('.'); - if (list.length > 1) list.pop(); - url = list.join('.'); - } - return url; - }; + // const trimURL = url => { + // const updateURLS = lib.updateURLS; + // for (const key in updateURLS) { + // const updateURL = updateURLS[key]; + // if (url == updateURL) return lib.configMenu.general.config.update_link.item[key]; + // } + // let index = url.indexOf('://'); + // if (index != -1) url = url.slice(index + 3); + // index = url.indexOf('/'); + // if (index != -1) url = url.slice(0, index); + // if (url.length > 15) { + // const list = url.split('.'); + // if (list.length > 1) list.shift(); + // url = list.join('.'); + // } + // if (url.length > 15) { + // const list = url.split('.'); + // if (list.length > 1) list.pop(); + // url = list.join('.'); + // } + // return url; + // }; li1.innerHTML = '游戏版本:' + lib.version + '

'; li2.innerHTML = '素材版本:' + (lib.config.asset_version || '无') + '

'; - li3.innerHTML = '更新地址:' + trimURL(lib.config.updateURL || lib.updateURL) + '

'; + // li3.innerHTML = '更新地址:' + trimURL(lib.config.updateURL || lib.updateURL) + '

'; li3.style.whiteSpace = 'nowrap'; li3.style.display = 'none';// coding - var button1, button2, button3, button4, button5; - - game.checkForUpdate = function (forcecheck, dev) { - if (!dev && button1.disabled) { + var checkVersionButton, checkAssetButton, checkDevVersionButton/*, button4, button5*/; + + game.checkForUpdate = async function (forcecheck, dev) { + if (!dev && checkVersionButton.disabled) { return; } - else if (dev && button3.disabled) { - return; - } - else if (!game.download) { - alert('此版本不支持游戏内更新,请手动更新'); + else if (dev && checkDevVersionButton.disabled) { return; } else { if (dev) { - button3.innerHTML = '正在检查更新'; + checkDevVersionButton.innerHTML = '正在检查更新'; } else { - button1.innerHTML = '正在检查更新'; + checkVersionButton.innerHTML = '正在检查更新'; } - button3.disabled = true; - button1.disabled = true; - var goupdate = function (files, update) { - lib.version = update.version; - if (update.dev && !lib.config.debug) { - dev = 'nodev'; - } - lib.init.req('game/source.js', function () { - try { - eval(this.responseText); - if (!window.noname_source_list) { - throw ('err'); + checkDevVersionButton.disabled = true; + checkVersionButton.disabled = true; + + function refresh() { + checkVersionButton.disabled = false; + checkVersionButton.innerHTML = '检查游戏更新'; + checkDevVersionButton.disabled = false; + checkDevVersionButton.innerHTML = '更新到开发版'; + } + + if (!dev) { + getRepoTags() + .then(tags => tags.filter(tag => tag.name != 'v1998')[0]) + .then(tag => { + game.saveConfig('check_version', tag.name.slice(1)); + if (typeof lib.config['version_description_' + tag.name] == 'object') { + /** @type { ReturnType } */ + const description = lib.config['version_description_' + tag.name]; + return description; } - } - catch (e) { - alert('更新地址有误'); - console.log(e); - return; - } - - var updates = window.noname_source_list; - delete window.noname_source_list; - if (Array.isArray(files)) { - files.add('game/update.js'); - var files2 = []; - for (var i = 0; i < files.length; i++) { - var str = files[i].indexOf('*'); - if (str != -1) { - str = files[i].slice(0, str); - files.splice(i--, 1); - for (var j = 0; j < updates.length; j++) { - if (updates[j].startsWith(str)) { - files2.push(updates[j]); + else return getRepoTagDescription(tag.name); + }) + .then(description => { + // 保存版本信息 + if (typeof lib.config['version_description_' + description.name] != 'object') { + game.saveConfig('version_description_' + description.name, description); + } + const versionResult = checkVersion(lib.version, description.name); + if (versionResult === 0) { + // forcecheck: 为false的时候是自动检测更新的调用 + if (forcecheck === false || !confirm('版本已是最新,是否强制更新?')) { + refresh(); + return; + } + } + const str = versionResult > 0 ? (`有新版本${description.name}可用,是否下载?`) : (`本地版本${ lib.version }高于或等于github版本${description.name},是否强制下载?`); + const str2 = description.body; + if (navigator.notification && navigator.notification.confirm) { + navigator.notification.confirm( + str2, + function (index) { + if (index == 1) { + download(description); } - } - } - } - updates = files.concat(files2); - } - for (var i = 0; i < updates.length; i++) { - if (updates[i].startsWith('theme/') && !updates[i].includes('.css')) { - updates.splice(i--, 1); - } - else if (updates[i].startsWith('node_modules/') && !update.node) { - updates.splice(i--, 1); - } - } - - if (!ui.arena.classList.contains('menupaused')) { - ui.click.configMenu(); - ui.click.menuTab('其它'); - } - var p = button1.parentNode; - button1.remove(); - button3.remove(); - var span = document.createElement('span'); - var n1 = 0; - var n2 = updates.length; - span.innerHTML = '正在下载文件(' + n1 + '/' + n2 + ')'; - p.appendChild(span); - var finish = function () { - span.innerHTML = '游戏更新完毕(' + n1 + '/' + n2 + ')'; - p.appendChild(document.createElement('br')); - var button = document.createElement('button'); - button.innerHTML = '重新启动'; - button.onclick = game.reload; - button.style.marginTop = '8px'; - p.appendChild(button); - }; - game.multiDownload(updates, function () { - n1++; - span.innerHTML = '正在下载文件(' + n1 + '/' + n2 + ')'; - }, function (e) { - game.print('下载失败:' + e.source); - }, function () { - setTimeout(finish, 500); - }, null, dev); - }, function () { - alert('更新地址有误'); - }, true); - }; - - lib.init.req('game/update.js', function () { - try { - eval(this.responseText); - if (!window.noname_update) { - throw ('err'); - } - } - catch (e) { - alert('更新地址有误'); - console.log(e); - return; - } - - var update = window.noname_update; - delete window.noname_update; - if (forcecheck === false) { - if (update.version == lib.config.check_version) { - return; - } - } - game.saveConfig('check_version', update.version); - var goon = true; - if (!dev) { - if (update.version.includes('beta') || update.version == lib.version) { - goon = false; - } - } - if (goon) { - var files = null; - var version = lib.version; - if (Array.isArray(update.dev) && dev) { - files = update.dev; - } - else if (Array.isArray(update.files) && update.update && !dev) { - var version1 = version.split('.'); - var version2 = update.update.split('.'); - for (var i = 0; i < version1.length && i < version2.length; i++) { - if (version2[i] > version1[i]) { - files = false; break; - } - else if (version1[i] > version2[i]) { - files = update.files.slice(0); break; - } - } - if (files === null) { - if (version1.length >= version2.length) { - files = update.files.slice(0); - } - } - } - var str; - if (dev) { - str = '开发版仅供测试使用,可能存在风险,是否确定更新?'; - } - else { - str = '有新版本' + update.version + '可用,是否下载?'; - } - if (navigator.notification && navigator.notification.confirm) { - var str2; - if (dev) { - str2 = str; - str = '更新到开发版'; + else refresh(); + }, + str, + ['确定', '取消'] + ); } else { - str2 = update.changeLog[0]; - for (var i = 1; i < update.changeLog.length; i++) { - if (update.changeLog[i].indexOf('://') == -1) { - str2 += ';' + update.changeLog[i]; - } + if (confirm(str + '\n' + str2)) { + download(description); } + else refresh(); } - navigator.notification.confirm( - str2, - function (index) { - if (index == 1) { - goupdate(files, update); - } - else { - button1.disabled = false; - button1.innerHTML = '检查游戏更新'; - button3.disabled = false; - button3.innerHTML = '更新到开发版'; - } - }, - str, - ['确定', '取消'] - ); - } - else { - if (confirm(str)) { - goupdate(files, update); - } - else { - button1.disabled = false; - button1.innerHTML = '检查游戏更新'; - button3.disabled = false; - button3.innerHTML = '更新到开发版'; - } + }) + .catch(e => { + alert('获取更新失败: ' + e); + refresh(); + }); + } else { + if (confirm('将要下载dev版本的完整包,是否继续?')) { + download({ + name: 'noname-PR-Branch', + assets: [], + zipball_url: 'https://ghproxy.cc/https://github.com/libccy/noname/archive/PR-Branch.zip' + }); + } + } + function download(description) { + const progress = createProgress('正在更新' + description.name, 1, description.name + '.zip'); + let unZipProgress; + let url = description.zipball_url; + if (Array.isArray(description.assets) && description.assets.length > 0) { + const coreZipData = description.assets.find(v => v.name == 'noname.core.zip'); + if (coreZipData && confirm(`检测到该版本(${description.name})有离线包资源,是否改为下载离线包资源?否则将下载完整包资源`)) { + url = 'https://ghproxy.cc/' + coreZipData.browser_download_url; } } - else { - alert('当前版本已是最新'); - button1.disabled = false; - button1.innerHTML = '检查游戏更新'; - button3.disabled = false; - button3.innerHTML = '更新到开发版'; - } - }, function () { - if (forcecheck === false) { - return; - } - alert('连接失败'); - button1.disabled = false; - button1.innerHTML = '检查游戏更新'; - button3.disabled = false; - button3.innerHTML = '更新到开发版'; - }, true); + request(url, (receivedBytes, total, filename) => { + if (typeof filename == 'string') { + progress.setFileName(filename); + } + let received = 0, max = 0; + if (total) { + max = +(total / (1024 * 1024)).toFixed(1) + } else { + max = 1000; + } + received = +(receivedBytes / (1024 * 1024)).toFixed(1); + if (received > max) max = received; + progress.setProgressMax(max); + progress.setProgressValue(received); + }).then(async blob => { + progress.remove(); + await import('../../../../../game/jszip.js'); + const zip = new window.JSZip().load(await blob.arrayBuffer()); + const entries = Object.entries(zip.files); + let root; + const hiddenFileFlags = ['.', '_']; + unZipProgress = createProgress('正在解压' + progress.getFileName(), entries.length); + let i = 0; + for (const [key, value] of entries) { + // 第一个是文件夹的话,就是根文件夹 + if (i == 0 && value.dir && !description.name.includes('noname.core.zip')) { + root = key; + } + unZipProgress.setProgressValue(i++); + const fileName = typeof root == 'string' && key.startsWith(root) ? key.replace(root, '') : key; + if (hiddenFileFlags.includes(fileName[0])) continue; + if (value.dir) { + await game.promises.createDir(fileName); + continue; + } + unZipProgress.setFileName(fileName); + const [path, name] = [fileName.split('/').slice(0, -1).join('/'), fileName.split('/').slice(-1).join('/')]; + game.print(`${fileName}(${i}/${entries.length})`); + await game.promises.writeFile(value.asArrayBuffer(), path, name) + .catch(async e => { + // 特殊处理 + if (name == 'noname-server.exe' && e.message.includes('resource busy or locked') && location.protocol.startsWith('http')) { + if (typeof window.require == 'function' && + typeof window.process == 'object' && + typeof window.__dirname == 'string') { + return new Promise((resolve, reject) => { + const cp = require('child_process'); + cp.exec(`taskkill /IM noname-server.exe /F`, e => { + if (e) reject(e); + else game.promises.writeFile(value.asArrayBuffer(), path, name).then(() => { + cp.exec(`start /b ${__dirname}\\noname-server.exe -platform=electron`, () => { }); + function loadURL() { + let myAbortController = new AbortController();; + let signal = myAbortController.signal; + setTimeout(() => myAbortController.abort(), 2000); + fetch(`http://localhost:8089/app.html`, { signal }) + .then(({ ok }) => { + if (ok) resolve(null); + else throw new Error('fetch加载失败'); + }) + .catch(() => loadURL()); + } + loadURL(); + }).catch(reject); + }); + }); + } + } else throw e; + }); + } + unZipProgress.remove(); + // await import('../../../../../game/update.js'); + // if (Array.isArray(window.noname_asset_list)) { + // game.saveConfig('asset_version', window.noname_asset_list[0]); + // delete window.noname_asset_list; + // } + if (confirm('更新完成,是否重启?')) { + game.reload(); + } + refresh(); + }).catch(e => { + if (progress.parentNode) progress.remove(); + if (unZipProgress && unZipProgress.parentNode) unZipProgress.remove(); + refresh(); + throw e; + }); + } } }; + game.checkForAssetUpdate = function (type) { - if (button2.disabled) { + return alert('暂不支持更新素材,请点击检查游戏更新按钮下载完整包'); + if (checkAssetButton.disabled) { return; } else if (game.download) { - button2.innerHTML = '正在检查更新'; - button2.disabled = true; + checkAssetButton.innerHTML = '正在检查更新'; + checkAssetButton.disabled = true; lib.init.req('game/asset.js', function () { try { eval(this.responseText); @@ -455,12 +430,12 @@ export const otherMenu = function (connectMenu) { game.print(updates); game.saveConfig('asset_version', asset_version); alert('素材已是最新'); - button2.disabled = false; - button2.innerHTML = '检查素材更新'; + checkAssetButton.disabled = false; + checkAssetButton.innerHTML = '检查素材更新'; return; } - var p = button2.parentNode; - button2.remove(); + var p = checkAssetButton.parentNode; + checkAssetButton.remove(); var span = document.createElement('span'); span.style.whiteSpace = 'nowrap'; var n1 = 0; @@ -515,8 +490,8 @@ export const otherMenu = function (connectMenu) { game.checkFileList(updates, proceed); }, function () { alert('连接失败'); - button2.disabled = false; - button2.innerHTML = '检查素材更新'; + checkAssetButton.disabled = false; + checkAssetButton.innerHTML = '检查素材更新'; }, true); } else { @@ -524,19 +499,19 @@ export const otherMenu = function (connectMenu) { } }; - button1 = document.createElement('button'); - button1.innerHTML = '检查游戏更新'; - button1.onclick = game.checkForUpdate; - li1.lastChild.appendChild(button1); + checkVersionButton = document.createElement('button'); + checkVersionButton.innerHTML = '检查游戏更新'; + checkVersionButton.onclick = game.checkForUpdate; + li1.lastChild.appendChild(checkVersionButton); - button3 = document.createElement('button'); - button3.innerHTML = '更新到开发版'; - button3.style.marginLeft = '5px'; - button3.onclick = function () { + checkDevVersionButton = document.createElement('button'); + checkDevVersionButton.innerHTML = '更新到开发版'; + checkDevVersionButton.style.marginLeft = '5px'; + checkDevVersionButton.onclick = function () { game.checkForUpdate(null, true); }; // if(lib.config.dev){ - // li1.lastChild.appendChild(button3); + // li1.lastChild.appendChild(checkDevVersionButton); // } (function () { @@ -572,53 +547,53 @@ export const otherMenu = function (connectMenu) { ui.updateUpdate(); }()); - button4 = document.createElement('button'); - button4.innerHTML = '设置更新地址'; - button4.onclick = function () { - game.prompt('设置更新地址', function (str) { - if (str) { - game.saveConfig('updateURL', str); - li3.querySelector('span').innerHTML = trimURL(str); - button5.style.display = ''; - button6.style.display = 'none'; - } - }); - }; + // button4 = document.createElement('button'); + // button4.innerHTML = '设置更新地址'; + // button4.onclick = function () { + // game.prompt('设置更新地址', function (str) { + // if (str) { + // game.saveConfig('updateURL', str); + // li3.querySelector('span').innerHTML = trimURL(str); + // button5.style.display = ''; + // button6.style.display = 'none'; + // } + // }); + // }; // li3.lastChild.appendChild(button4); - var button6 = document.createElement('button'); - button6.innerHTML = '设为备用镜像'; - button6.style.display = 'none';// coding + // var button6 = document.createElement('button'); + // button6.innerHTML = '设为备用镜像'; + // button6.style.display = 'none';// coding // button6.style.marginLeft='5px'; - button6.onclick = function () { - game.saveConfig('updateURL', lib.mirrorURL); - button5.style.display = ''; - button6.style.display = 'none'; - li3.querySelector('span').innerHTML = trimURL(lib.mirrorURL); - }; - li3.lastChild.appendChild(button6); + // button6.onclick = function () { + // game.saveConfig('updateURL', lib.mirrorURL); + // // button5.style.display = ''; + // button6.style.display = 'none'; + // li3.querySelector('span').innerHTML = trimURL(lib.mirrorURL); + // }; + // li3.lastChild.appendChild(button6); - button5 = document.createElement('button'); - button5.innerHTML = '设为默认镜像'; + // button5 = document.createElement('button'); + // button5.innerHTML = '设为默认镜像'; // button5.style.marginLeft='5px'; - button5.onclick = function () { - game.saveConfig('updateURL'); - button5.style.display = 'none'; - button6.style.display = ''; - li3.querySelector('span').innerHTML = trimURL(lib.updateURL); - }; - li3.lastChild.appendChild(button5); - if (!lib.config.updateURL) { - button5.style.display = 'none'; - } - else { - button6.style.display = 'none'; - } + // button5.onclick = function () { + // game.saveConfig('updateURL'); + // button5.style.display = 'none'; + // button6.style.display = ''; + // li3.querySelector('span').innerHTML = trimURL(lib.updateURL); + // }; + // li3.lastChild.appendChild(button5); + // if (!lib.config.updateURL) { + // button5.style.display = 'none'; + // } + // else { + // button6.style.display = 'none'; + // } - button2 = document.createElement('button'); - button2.innerHTML = '检查素材更新'; - button2.onclick = game.checkForAssetUpdate; - li2.lastChild.appendChild(button2); + checkAssetButton = document.createElement('button'); + checkAssetButton.innerHTML = '检查素材更新'; + checkAssetButton.onclick = game.checkForAssetUpdate; + li2.lastChild.appendChild(checkAssetButton); var span1 = ui.create.div('.config.more', '选项
>
'); span1.style.fontSize = 'small'; @@ -626,39 +601,27 @@ export const otherMenu = function (connectMenu) { span1.toggle = function () { if (!this.classList.toggle('on')) { game.saveConfig('asset_toggle_off', true); - span2.style.display = 'none'; - span2_br.style.display = 'none'; - span2_check.style.display = 'none'; - span3.style.display = 'none'; - span3_br.style.display = 'none'; - span3_check.style.display = 'none'; - span4.style.display = 'none'; - span4_br.style.display = 'none'; - span4_check.style.display = 'none'; - span5.style.display = 'none'; - span5_br.style.display = 'none'; - span5_check.style.display = 'none'; - span6.style.display = 'none'; - span6_br.style.display = 'none'; - span6_check.style.display = 'none'; + [ + span2, span2_br, span2_check, + span3, span3_br, span3_check, + span4, span4_br, span4_check, + span5, span5_br, span5_check, + span6, span6_br, span6_check, + ].forEach(item => HTMLDivElement.prototype.css.call(item, { + display: 'none' + })); } else { game.saveConfig('asset_toggle_off'); - span2.style.display = ''; - span2_br.style.display = ''; - span2_check.style.display = ''; - span3.style.display = ''; - span3_br.style.display = ''; - span3_check.style.display = ''; - span4.style.display = ''; - span4_br.style.display = ''; - span4_check.style.display = ''; - span5.style.display = ''; - span5_br.style.display = ''; - span5_check.style.display = ''; - span6.style.display = ''; - span6_br.style.display = ''; - span6_check.style.display = ''; + [ + span2, span2_br, span2_check, + span3, span3_br, span3_check, + span4, span4_br, span4_check, + span5, span5_br, span5_check, + span6, span6_br, span6_check, + ].forEach(item => HTMLDivElement.prototype.css.call(item, { + display: '' + })); } }; span1.listen(span1.toggle); @@ -751,22 +714,16 @@ export const otherMenu = function (connectMenu) { game.saveConfig('asset_full', this.checked); }; li2.lastChild.appendChild(span6_check); - - span2.style.display = 'none'; - span2_br.style.display = 'none'; - span2_check.style.display = 'none'; - span3.style.display = 'none'; - span3_br.style.display = 'none'; - span3_check.style.display = 'none'; - span4.style.display = 'none'; - span4_br.style.display = 'none'; - span4_check.style.display = 'none'; - span5.style.display = 'none'; - span5_br.style.display = 'none'; - span5_check.style.display = 'none'; - span6.style.display = 'none'; - span6_br.style.display = 'none'; - span6_check.style.display = 'none'; + + [ + span2, span2_br, span2_check, + span3, span3_br, span3_check, + span4, span4_br, span4_check, + span5, span5_br, span5_check, + span6, span6_br, span6_check, + ].forEach(item => HTMLDivElement.prototype.css.call(item, { + display: 'none' + })); ul.appendChild(li1); ul.appendChild(li2);