添加对比素材功能,增加储存git token功能

This commit is contained in:
nonameShijian 2024-04-06 20:16:13 +08:00
parent c3fc7ae7e6
commit 3f3c07b0ec
3 changed files with 204 additions and 177 deletions

View File

@ -7,12 +7,6 @@ export class GamePromises extends Uninstantable {
*
* : 由于参数列表是随意的在这里我准备限制一下这个函数的参数顺序
*
* @type {{
* (title: string): Promise<string | false>;
* (title: string, forced: true): Promise<string>;
* (alertOption: 'alert', title: string): Promise<true>;
* }}
*
* @param { string } [title] 设置prompt标题与input内容
* @param { boolean } [forced] 为true的话将没有"取消按钮"
* @param { string } alertOption 设置prompt是否模拟alert
@ -25,6 +19,18 @@ export class GamePromises extends Uninstantable {
* ```
* @returns { Promise<string> }
*/
/**
* @overload
* @param { string } title
* @returns { Promise<string | false> }
*/
/**
* @overload
* @param { string } title
* @param { boolean } [forced]
* @returns { Promise<string> }
*
*/
// @ts-ignore
static prompt(alertOption, title, forced) {
return new Promise((resolve, reject) => {

View File

@ -1,4 +1,4 @@
import { ui } from '../../noname.js';
import { lib, ui, game } from '../../noname.js';
// https://github.com/libccy/noname/archive/refs/tags/v1.10.10.zip
@ -14,16 +14,36 @@ const defaultHeaders = {
'Accept': 'application/vnd.github.v3+json',
// 根据GitHub API的要求添加适当的认证头信息
// 如果公共仓库则无需认证私有仓库需提供token
// 'Authorization': `Bearer ${YOUR_GITHUB_PERSONAL_ACCESS_TOKEN}`
// 'Authorization': `token ${YOUR_GITHUB_PERSONAL_ACCESS_TOKEN}`
};
const defaultResponse = response => {
if (localStorage.getItem('noname_authorization')) {
defaultHeaders['Authorization'] = `token ${localStorage.getItem('noname_authorization')}`;
}
export async function gainAuthorization() {
if (!localStorage.getItem('noname_authorization') && !sessionStorage.getItem('noname_authorization')) {
const result = await game.promises.prompt('请输入您github的token以解除访问每小时60次的限制');
if (typeof result == 'string') {
localStorage.setItem('noname_authorization', result);
defaultHeaders['Authorization'] = `token ${localStorage.getItem('noname_authorization')}`;
} else {
sessionStorage.setItem('noname_authorization', "false");
}
}
}
const defaultResponse = async (/** @type {Response} */ 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);
// @ts-ignore
console.log(`限制重置时间`, (new Date(reset * 1000)).toLocaleString());
if (Number(remaining) === 0 && !sessionStorage.getItem('noname_authorization') && confirm(`您达到了每小时${limit}次的访问限制是否输入您github的token以获取更高的请求总量限制`)) {
await gainAuthorization();
}
};
/**
@ -140,6 +160,9 @@ export function checkVersion(ver1, ver2) {
* ```
*/
export async function getRepoTags(options = { username: 'libccy', repository: 'noname' }) {
if (!localStorage.getItem('noname_authorization')) {
await gainAuthorization();
}
const { username = 'libccy', repository = 'noname', accessToken } = options;
const headers = Object.assign({}, defaultHeaders);
if (accessToken) {
@ -147,7 +170,7 @@ export async function getRepoTags(options = { username: 'libccy', repository: 'n
}
const url = `https://api.github.com/repos/${username}/${repository}/tags`;
const response = await fetch(url, { headers });
defaultResponse(response);
await defaultResponse(response);
if (response.ok) {
const data = await response.json();
return data;
@ -172,6 +195,9 @@ export async function getRepoTags(options = { username: 'libccy', repository: 'n
*/
export async function getRepoTagDescription(tagName, options = { username: 'libccy', repository: 'noname' }) {
if (!localStorage.getItem('noname_authorization')) {
await gainAuthorization();
}
const { username = 'libccy', repository = 'noname', accessToken } = options;
const headers = Object.assign({}, defaultHeaders);
if (accessToken) {
@ -179,7 +205,7 @@ export async function getRepoTagDescription(tagName, options = { username: 'libc
}
const apiUrl = `https://api.github.com/repos/${username}/${repository}/releases/tags/${tagName}`;
const response = await fetch(apiUrl, { headers });
defaultResponse(response);
await defaultResponse(response);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
@ -220,7 +246,7 @@ export async function getRepoTagDescription(tagName, options = { username: 'libc
* @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' }> }
* @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()
@ -229,6 +255,9 @@ export async function getRepoTagDescription(tagName, options = { username: 'libc
* ```
*/
export async function getRepoFilesList(path = '', branch, options = { username: 'libccy', repository: 'noname' }) {
if (!localStorage.getItem('noname_authorization')) {
await gainAuthorization();
}
const { username = 'libccy', repository = 'noname', accessToken } = options;
const headers = Object.assign({}, defaultHeaders);
if (accessToken) {
@ -236,20 +265,20 @@ export async function getRepoFilesList(path = '', branch, options = { username:
}
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));
const pathURL = new URL(url);
const searchParams = new URLSearchParams(pathURL.search.slice(1));
if (searchParams.has('ref')) {
throw new TypeError(`设置了branch参数后不应在path参数内拼接ref`);
}
searchParams.append('ref', branch);
url = searchParams.toString();
url = pathURL.origin + pathURL.pathname + "?" + searchParams.toString();
}
const response = await fetch(url, { headers });
defaultResponse(response);
await 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,
@ -261,20 +290,68 @@ export async function getRepoFilesList(path = '', branch, options = { username:
}));
};
/**
*
* 获取仓库指定分支和指定目录内的所有文件和目录
* @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' }[]> }
* @example
* ```js
* flattenRepositoryFiles()
* .then(files => console.log(files))
* .catch(error => console.error('Failed to fetch files:', error));
* ```
*/
export async function flattenRepositoryFiles(path = '', branch, options = { username: 'libccy', repository: 'noname' }) {
/**
* @type { { download_url: string, name: string, path: string, sha: string, size: number, type: 'file' }[] }
*/
const flattenedFiles = [];
/**
* @param {({ 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"; })[]} contents
*/
async function traverseDirectory(contents) {
for (const item of contents) {
if (item.type === 'file') {
flattenedFiles.push(item);
} else if (item.type === 'dir') {
// 获取子目录下的文件列表
const subDirFiles = await getRepoFilesList(item.path, branch, options);
// 递归处理子目录中的文件和子目录
await traverseDirectory(subDirFiles);
}
}
return flattenedFiles;
}
// 开始遍历初始dir目录下的内容
const allFiles = await traverseDirectory(await getRepoFilesList(path, branch, options));
// 返回不含文件夹的扁平化文件列表
return allFiles;
}
/**
* 请求一个文件而不是直接储存为文件
* @param { string } url
* @param { (receivedBytes: number, total?:number, filename?: string) => void } [onProgress]
* @param { RequestInit } [options={}]
* @example
* ```js
* await getRepoTagDescription('v1.10.10').then(({ zipball_url }) => request(zipball_url));
* ```
*/
export async function request(url, onProgress) {
const response = await fetch(url, {
export async function request(url, onProgress, options = {}) {
const response = await fetch(url, Object.assign({
// 告诉服务器我们期望得到范围请求的支持
headers: { 'Range': 'bytes=0-' },
});
}, options));
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);

View File

@ -23,8 +23,10 @@ import {
getRepoTags,
getRepoTagDescription,
getRepoFilesList,
flattenRepositoryFiles,
request,
createProgress
createProgress,
gainAuthorization
} from "../../../../library/update.js"
export const otherMenu = function (connectMenu) {
@ -224,12 +226,14 @@ export const otherMenu = function (connectMenu) {
refresh();
});
} else {
if (confirm('将要下载dev版本的完整包是否继续?')) {
if (confirm('将要直接下载dev版本的完整包是否继续?')) {
download({
name: 'noname-PR-Branch',
assets: [],
zipball_url: 'https://ghproxy.cc/https://github.com/libccy/noname/archive/PR-Branch.zip'
});
} else {
refresh();
}
}
function download(description) {
@ -332,167 +336,107 @@ export const otherMenu = function (connectMenu) {
}
};
game.checkForAssetUpdate = function (type) {
return alert('暂不支持更新素材,请点击检查游戏更新按钮下载完整包');
game.checkForAssetUpdate = async function () {
if (checkAssetButton.disabled) {
return;
}
else if (game.download) {
// if (!confirm('请保证一小时内没有进行过素材和游戏更新,否则会请求失败,是否继续?')) {
// return;
// }
if (!localStorage.getItem('noname_authorization') && !sessionStorage.getItem('noname_authorization')) {
if (confirm('素材更新或许会直接超过每小时的访问限制是否输入您github的token以解除访问每小时60次的限制')) await gainAuthorization();
}
checkAssetButton.innerHTML = '正在检查更新';
checkAssetButton.disabled = true;
lib.init.req('game/asset.js', function () {
try {
eval(this.responseText);
if (!window.noname_asset_list || !window.noname_skin_list) {
throw ('err');
}
}
catch (e) {
alert('更新地址有误');
console.log(e);
return;
}
var updates = window.noname_asset_list;
delete window.noname_asset_list;
var skins = window.noname_skin_list;
delete window.noname_skin_list;
var asset_version = updates.shift();
var skipcharacter = [], skipcard = ['tiesuo_mark', 'shield'];
if (!lib.config.asset_full) {
for (var i = 0; i < lib.config.all.sgscharacters.length; i++) {
var pack = lib.characterPack[lib.config.all.sgscharacters[i]];
for (var j in pack) {
skipcharacter.add(j);
}
}
for (var i = 0; i < lib.config.all.sgscards.length; i++) {
var pack = lib.cardPack[lib.config.all.sgscards[i]];
if (pack) {
skipcard = skipcard.concat(pack);
}
}
}
for (var i = 0; i < updates.length; i++) {
switch (updates[i].slice(0, 5)) {
case 'image': {
if (!lib.config.asset_full) {
if (!lib.config.asset_image) {
updates.splice(i--, 1);
}
else {
if (updates[i].startsWith('image/character')) {
if (updates[i].indexOf('jun_') != 16 && updates[i].indexOf('gz_') != 16 && !skipcharacter.includes(updates[i].slice(16, updates[i].lastIndexOf('.')))) {
updates.splice(i--, 1);
}
}
else if (updates[i].startsWith('image/card')) {
let cardname = updates[i].slice(11, updates[i].lastIndexOf('.'));
if (lib.card[cardname] && !skipcard.includes(cardname)) {
updates.splice(i--, 1);
}
}
else if (updates[i].startsWith('image/mode/stone')) {
updates.splice(i--, 1);
}
}
}
break;
}
case 'audio': {
if (!lib.config.asset_audio) {
updates.splice(i--, 1);
}
break;
}
case 'font/': {
if (!lib.config.asset_font) {
updates.splice(i--, 1);
}
}
}
}
if (lib.config.asset_skin) {
for (var i in skins) {
for (var j = 1; j <= skins[i]; j++) {
updates.push('image/skin/' + i + '/' + j + '.jpg');
}
}
}
if (!ui.arena.classList.contains('menupaused')) {
ui.click.configMenu();
ui.click.menuTab('其它');
}
var proceed = function () {
if (updates.length == 0) {
game.print(updates);
game.saveConfig('asset_version', asset_version);
alert('素材已是最新');
checkAssetButton.disabled = false;
checkAssetButton.innerHTML = '检查素材更新';
return;
}
var p = checkAssetButton.parentNode;
checkAssetButton.remove();
var span = document.createElement('span');
span.style.whiteSpace = 'nowrap';
var n1 = 0;
var n2 = updates.length;
span.innerHTML = '正在下载素材(' + n1 + '/' + n2 + '';
span1.remove();
span2.remove();
span2_check.remove();
span3.remove();
span3_check.remove();
span4.remove();
span4_check.remove();
span5.remove();
span5_check.remove();
span6.remove();
span6_check.remove();
span2_br.remove();
span3_br.remove();
span4_br.remove();
span5_br.remove();
span6_br.remove();
p.appendChild(span);
var br6 = ui.create.node('br');
var span7 = ui.create.div('.hrefnode', '详细信息');
span7.style.marginTop = '6px';
span7.listen(ui.click.consoleMenu);
p.appendChild(br6);
p.appendChild(span7);
var finish = function () {
if (n1 == n2) {
game.saveConfig('asset_version', asset_version);
}
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);
});
};
game.checkFileList(updates, proceed);
}, function () {
alert('连接失败');
checkAssetButton.disabled = false;
function refresh() {
checkAssetButton.innerHTML = '检查素材更新';
}, true);
checkAssetButton.disabled = false;
}
// 暂定更新这四个文件夹
const assetDirs = ['audio', 'font', 'image', 'theme'];
const files = await Promise.all(assetDirs.map(dir => flattenRepositoryFiles(dir)));
/**
* @param { any[] } arr
* @param { Function } predicate
*/
const asyncFilter = async (arr, predicate) => {
// @ts-ignore
const results = await Promise.all(arr.map(predicate));
// @ts-ignore
return arr.filter((_v, index) => results[index]);
}
// @ts-ignore
const result = await asyncFilter(files.flat(), async v => {
return v.size != (await game.promises.readFile(v.path)).length;
}).then(arr => arr.map(v => v.path));
console.log(result);
const progress = createProgress('正在更新素材包.zip');
/**
* @type {progress}
*/
let unZipProgress;
request('noname.unitedrhythmized.club/api', (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);
}, {
method: 'post',
body: JSON.stringify({
fileList: result
})
}).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);
}
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;
});
}
else {
alert('此版本不支持游戏内更新素材,请手动更新');
@ -501,7 +445,7 @@ export const otherMenu = function (connectMenu) {
checkVersionButton = document.createElement('button');
checkVersionButton.innerHTML = '检查游戏更新';
checkVersionButton.onclick = game.checkForUpdate;
checkVersionButton.onclick = () => game.checkForUpdate(null);
li1.lastChild.appendChild(checkVersionButton);
checkDevVersionButton = document.createElement('button');
@ -592,7 +536,7 @@ export const otherMenu = function (connectMenu) {
checkAssetButton = document.createElement('button');
checkAssetButton.innerHTML = '检查素材更新';
checkAssetButton.onclick = game.checkForAssetUpdate;
checkAssetButton.onclick = () => game.checkForAssetUpdate();
li2.lastChild.appendChild(checkAssetButton);
var span1 = ui.create.div('.config.more', '选项 <div>&gt;</div>');