Update reader.

This commit is contained in:
Bruce
2026-04-04 19:27:45 +08:00
parent f9f4db3f6c
commit bf54d5a531
22 changed files with 3405 additions and 21 deletions

View File

@@ -396,7 +396,7 @@
</div>
<div class="controls">
<div class="checkbox">
<input type="checkbox" id="preinst-enablelaunch" class="win-checkbox">
<input type="checkbox" id="preinst-enablelaunch" class="win-checkbox" style="margin-left: 0;">
<label for="preinst-enablelaunch" data-res-byname="IDS_LAUNCHWHENREADY"></label>
</div>
<div class="command">

View File

@@ -118,4 +118,12 @@
return Bridge.String.toupper(this);
};
}
if (typeof String.prototype.format !== "function") {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] !== "undefined" ? args[number] : match;
});
};
}
})(this);

View File

@@ -363,6 +363,19 @@
));
}
};
Object.defineProperty(this, "length", {
get: function() {
return _list.length;
},
enumerable: true,
});
this.getDatas = function() { return _list; }
this.forEach = function(callback, args) {
if (typeof callback !== "function") return;
for (var i = 0; i < _list.length; i++) {
callback.apply(this, [_list[i], i].concat(Array.prototype.slice.call(arguments, 1)));
}
};
this._getKey = getKey;
}
var MAX_ANIMATE_COUNT = 100;
@@ -609,10 +622,16 @@
});
PMDataListView.prototype._updateEmptyView = function() {
if (!this._emptyView) return;
// container 中是否还有 item
var hasItem = this.container.children.length > 0;
var itemVisibleLength = 0;
for (var i = 0; i < this.container.children.length; i++) {
var child = this.container.children[i];
if (!child) continue;
if (child.style.display !== "none" && child.style.display !== "hidden" && child.style.opacity !== 0 && child.style.visibility !== "hidden") {
itemVisibleLength++;
}
}
hasItem = hasItem && itemVisibleLength > 0;
if (hasItem) {
if (this._emptyView.parentNode) {
this._emptyView.style.display = "none";
@@ -802,6 +821,7 @@
};
PMDataListView.prototype.refresh = function() {
this._refreshVisibility();
this._updateEmptyView();
};
global.DataView.ChangeEvent = PMChangeEvent;
global.DataView.DataSource = PMDataSource;

562
shared/html/js/dboxapi.js Normal file
View File

@@ -0,0 +1,562 @@
/**
* DBox API
* Docs: https://dbox.tools/api/docs
*/
(function(global) {
"use strict";
function joinUrl(url, path) {
if (url.charAt(url.length - 1) === "/") {
url = url.slice(0, -1);
}
if (path.charAt(0) === "/") {
path = path.slice(1);
}
return url + "/" + path;
}
function buildParams(params) {
var queryString = "";
var keys = Object.keys(params);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = params[key];
if (value === null || value === void 0) {
continue;
}
queryString += encodeURIComponent(key) + "=" + encodeURIComponent(value) + "&";
}
return queryString.slice(0, -1);
}
var baseUrl = "https://dbox.tools/";
var baseApiUrl = joinUrl(baseUrl, "api");
var dboxApi = {
/**
* @enum {string} DBox.API.System
* @readonly
*/
System: {
/** @type {string} Xbox */
xbox: "XBOX",
/** @type {string} Xbox 360 */
xbox360: "XBOX360",
/** @type {string} Xbox One */
xboxOne: "XBOXONE",
/** @type {string} Xbox Series X */
xboxSeriesX: "XBOXSERIESX",
/** @type {string} PC */
pc: "PC",
/** @type {string} PS3 */
mobile: "MOBILE"
},
Discs: {
/**
* Get and filter discs
* @param {string | null} name query
* @param {DBox.API.System} system query
* @param {integer | number} [limit] query, default: 100
* @param {integer | number} [offset] query, default: 0
* @returns {string} request URL, method: GET
*/
getDiscs: function(name, system, limit, offset) {
var params = {};
if (name) params.name = name;
if (system) params.system = system;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "discs") +
"?" + buildParams(params);
},
/**
* Get single disc by DBox ID
* @param {integer | number} discId path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscById: function(discId) {
return joinUrl(baseApiUrl, "discs/" + discId);
},
/**
* Get single disc by Redump ID
* @param {integer | number} redumpId path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByRedumpId: function(redumpId) {
return joinUrl(baseApiUrl, "discs/redump/" + redumpId);
},
/**
* Get single disc by its XMID (OG XBOX only)
* @param {string} xmid path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByXMId: function(xmid) {
return joinUrl(baseApiUrl, "discs/xmid/" + xmid);
},
/**
* Get single disc by its XeMID (XBOX 360 only)
* @param {string} xeMid path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByXeMId: function(xeMid) {
return joinUrl(baseApiUrl, "discs/xemid/" + xeMid);
},
/**
* Get single disc by its media ID. Media ID v1 (XBOX & XBOX 360) and Media ID v2 (XBOX One & XBOX Series) are combined in a single query
* @param {string} mediaId path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByMediaId: function(mediaId) {
return joinUrl(baseApiUrl, "discs/media_id/" + mediaId);
},
/**
* Get the dirs and files (if available) for a disc. Filetype indicates if it is a supported filetype that has further metadata available in the database.
* @param {integer | number} discId path, required
* @returns {string} request URL, method: GET
*/
getDiscFiles: function(discId) {
return joinUrl(baseApiUrl, "discs/" + discId + "/files");
}
},
Releases: {
/**
* Get and filter releases
* @param {string | null} name query
* @param {string | null} edition query
* @param {string | null} barcode query
* @param {DBox.API.System} system query
* @param {integer | number} limit query, default: 100
* @param {integer | number} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getReleases: function(name, edition, barcode, system, limit, offset) {
var params = {};
if (name) params.name = name;
if (edition) params.edition = edition;
if (barcode) params.barcode = barcode;
if (system) params.system = system;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "releases") +
"?" + buildParams(params);
},
/**
* Get single release by DBox ID
* @param {string} releaseId path, required
* @returns {string} request URL, method: GET
*/
getSingleRelease: function(releaseId) {
return joinUrl(baseApiUrl, "releases/" + releaseId);
}
},
Files: {
/**
* Get all discs that contain a particular file. NOTE: not all discs have been parsed on file-level yet
* @param {string} md5 path, required
* @returns {string} request URL, method: GET
*/
getFiles: function(md5) {
return joinUrl(baseApiUrl, "files/" + md5 + "/discs");
},
/**
* Filter all distinct xbe files. NOTE: not all discs have been parsed on file-level yet
* @param {string | null} titleId query
* @param {integer | null} allowrdMedia query
* @param {integer | null} region query, Available values: 1, 2, 3, 4, 7, 2147483648
* @param {integer | null} gameRating query
* @param {integer | null} discNumber query
* @param {integer | null} version query
* @param {integer | number} limit query, default: 100
* @param {integer | number} offset query, default: 0
* @returns
*/
getXbeFiles: function(titleId, allowrdMedia, region, gameRating, discNumber, version, limit, offset) {
if (region !== null && region !== undefined) {
var valid = [1, 2, 3, 4, 7, 2147483648];
if (!valid.includes(region)) throw new Error('Invalid region');
}
var params = {};
if (titleId) params.title_id = titleId;
if (allowrdMedia) params.allowrd_media = allowrdMedia;
if (region) params.region = region;
if (gameRating) params.game_rating = gameRating;
if (discNumber) params.disc_number = discNumber;
if (version) params.version = version;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "files/xbe") +
"?" + buildParams(params);
},
/**
* Get xbe file by md5. NOTE: not all discs have been parsed on file-level yet
* @param {string} md5 path, required
* @returns {string} request URL, method: GET
*/
getXbeFileByMd5: function(md5) {
return joinUrl(baseApiUrl, "files/xbe/" + md5);
},
/**
* Get stfs file by md5. NOTE: not all discs have been parsed on file-level yet
* @param {string} md5 path, required
* @returns {string} request URL, method: GET
*/
getStfsFileByMd5: function(md5) {
return joinUrl(baseApiUrl, "files/stfs/" + md5);
},
},
TitleIDs: {
/**
* Get and filter title IDs
* @param {string | (string | null)} name query
* @param {DBox.API.System | null} system query
* @param {string | (string | null)($uuid)} bingId query
* @param {string | (string | null)($uuid)} serviceConfigId query
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getTitleIds: function(name, system, bingId, serviceConfigId, limit, offset) {
var params = {};
if (name) params.name = name;
if (system) params.system = system;
if (bingId) params.bing_id = bingId;
if (serviceConfigId) params.service_config_id = serviceConfigId;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "title_ids") +
"?" + buildParams(params);
},
/**
* Get single title ID by its hexadecimal value
* @param {string} titleId path, required
* @returns {string} request URL, method: GET
*/
getSingleTitleId: function(titleId) {
return joinUrl(baseApiUrl, "title_ids/" + titleId);
}
},
Achievements: {
/**
* Get achievements for a title-id. Xbox 360/GFWL only
* @param {string} titleId path, required
* @returns {string} request URL, method: GET
*/
getAchievementsV1: function(titleId) {
return joinUrl(baseApiUrl, "achievements/v1/" + titleId);
},
/**
* Get achievements for a title-id. Xbox One/Series only
* @param {string} titleId path, required
* @returns {string} request URL, method: GET
*/
getAchievementsV2: function(titleId) {
return joinUrl(baseApiUrl, "achievements/v2/" + titleId);
},
},
Marketplace: {
/**
* Get and filter marketplace products
* @param {integer | (integer | null)} productType query
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getProducts: function(productType, limit, offset) {
var params = {};
if (productType) params.product_type = productType;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "marketplace/products") +
"?" + buildParams(params);
},
/**
* Get single marketplace product by marketplace product ID
* @param {string} productId path, required
* @returns {string} request URL, method: GET
*/
getSingleProduct: function(productId) {
return joinUrl(baseApiUrl, "marketplace/products/" + productId);
},
/**
* Get children of a marketplace product
* @param {string} productId path, required
* @param {integer | (integer | null)} productType query
* @returns {string} request URL, method: GET
*/
getProductChildren: function(productId, productType) {
var params = {};
if (productType) params.product_type = productType;
return joinUrl(baseApiUrl, "marketplace/products/" + productId + "/children") +
"?" + buildParams(params);
},
/**
* Get and filter marketplace product instances
* @param {string | (string | null)($uuid)} productId query
* @param {string | (string | null)} hexOfferId query
* @param {integer | (integer | null)} licenseTypeId query
* @param {integer | (integer | null)} packageType query
* @param {integer | (integer | null)} gameRegion query
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getProductInstances: function(productId, hexOfferId, licenseTypeId, packageType, gameRegion, limit, offset) {
var params = {};
if (productId) params.product_id = productId;
if (hexOfferId) params.hex_offer_id = hexOfferId;
if (licenseTypeId) params.license_type_id = licenseTypeId;
if (packageType) params.package_type = packageType;
if (gameRegion) params.game_region = gameRegion;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "marketplace/product-instances") +
"?" + buildParams(params);
},
/**
* Get single marketplace product instance by marketplace product instance ID
* @param {string} instanceId path, required
* @returns {string} request URL, method: GET
*/
getProductInstanceById: function(instanceId) {
return joinUrl(baseApiUrl, "marketplace/product-instances/" + instanceId);
},
/**
* Get all marketplace product types
* @returns {string} request URL, method: GET
*/
getProductTypes: function() {
return joinUrl(baseApiUrl, "marketplace/product-types");
},
/**
* Get single marketplace category
* @param {integer} productTypeId path, required
* @returns {string} request URL, method: GET
*/
getSingleProductType: function(productTypeId) {
return joinUrl(baseApiUrl, "marketplace/product-types/" + productTypeId);
},
/**
* Get and filter all marketplace categories
* @param {integer | (integer | null)} parent query
* @returns {string} request URL, method: GET
*/
getCategories: function(parent) {
var params = {};
if (parent) params.parent = parent;
return joinUrl(baseApiUrl, "marketplace/categories") +
"?" + buildParams(params);
},
/**
* Get single marketplace category
* @param {integer} categoryId path, required
* @returns {string} request URL, method: GET
*/
getSingleCategory: function(categoryId) {
return joinUrl(baseApiUrl, "marketplace/categories/" + categoryId);
},
/**
* Get all marketplace locales
* @returns {string} request URL, method: GET
*/
getLocales: function() {
return joinUrl(baseApiUrl, "marketplace/locales");
},
/**
* Get single marketplace locale by locale string
* @param {string} localeId path, required
* @returns {string} request URL, method: GET
*/
getSingleLocale: function(localeId) {
return joinUrl(baseApiUrl, "marketplace/locales/" + localeId);
},
},
/**
* Enum for product types, used in store API
* @enum {string} DBox.API.ProductType
*/
ProductType: {
application: "Application",
avatarItem: "AvatarItem",
consumable: "Consumable",
durable: "Durable",
game: "Game",
movie: "Movie",
pass: "PASS",
tvSeries: "TvSeries",
tvSeason: "TvSeason",
tvEpisode: "TVEpisode",
unmanagedConsumable: "UnmanagedConsumable",
},
/**
* Enum for product families, used in store API
* @enum {string} DBox.API.ProductFamily
*/
ProductFamily: {
apps: "Apps",
avatars: "Avatars",
games: "Games",
movies: "Movies",
passes: "Passes",
tv: "TV",
},
/**
* Enum for order by, used in store API
* @enum {string} DBox.API.OrderBy
*/
OrderBy: {
productId: "product_id",
titleId: "title_id",
revisionId: "revision_id",
/** such as "23654onetwoonestudio.cctv_kdpw61jgbrs34" */
packageFamilyName: "package_family_name",
/** such as "23654onetwoonestudio.cctv" */
packageIdentityName: "package_identity_name",
},
/**
* Enum for order direction, used in store API
* @enum {string} DBox.API.OrderDirection
*/
OrderDirection: {
/** @type {string} 升序 */
asc: "asc",
/** @type {string} 降序 */
desc: "desc",
},
Store: {
/**
* Get store products
* @param {string | (string | null)} category query
* @param {string | (string | null)} titleId query
* @param {DBox.API.ProductType | null} productType query
* @param {DBox.API.ProductFamily | null} productFamily query
* @param {DBox.API.OrderBy | null} orderBy query, default: productId
* @param {DBox.API.OrderDirection | null} orderDirection query, default: asc
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getProducts: function(category, titleId, productType, productFamily, orderBy, orderDirection, limit, offset) {
var params = {};
if (category) params.category = category;
if (titleId) params.title_id = titleId;
if (productType) params.product_type = productType;
if (productFamily) params.product_family = productFamily;
if (orderBy) params.order_by = orderBy;
if (orderDirection) params.order_direction = orderDirection;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "store/products") +
"?" + buildParams(params);
},
/**
* Get single store product
* @param {string} productId path, required
* @returns {string} request URL, method: GET
*/
getSingleProduct: function(productId) {
return joinUrl(baseApiUrl, "store/products/" + productId);
},
/**
* Get all related products for a product id. Includes both child and parent relationships. Check the product-ids for relationship direction. The relationship_type is parent -> child direction. Same combinations can appear multiple times with different relationship types.
* @param {string} productId path, required
* @returns {string} request URL, method: GET
*/
getAllReleatedProducts: function(productId) {
return joinUrl(baseApiUrl, "store/products/" + productId + "/related");
},
/**
* Get single sku for store product
* @param {string} productId path, required
* @param {string} skuId path, required
* @returns {string} request URL, method: GET
*/
getSingleSkuFromProduct: function(productId, skuId) {
return joinUrl(baseApiUrl, "store/products/" + productId + "/sku/" + skuId);
}
}
};
function getXhr() {
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
}
/**
* Send an HTTP request to the DBox API and return a Promise that resolves with the response.
* @param {string} api DBox API
* @param {string} method
* @param {[boolean]} isAsync default: true
* @param {[string]} username
* @param {[string]} pwd
* @returns {Promise <XMLHttpRequest>} A Promise that resolves with the response.
*/
var dboxXHR = function(api, method, isAsync, username, pwd) {
method = method || "GET";
if (typeof isAsync === "undefined" || isAsync === null) isAsync = true;
var xhr = getXhr();
if (username && pwd) {
try {
xhr.open(method, api, isAsync, username, pwd);
} catch (e) {
xhr.open(method, api, isAsync);
}
} else {
xhr.open(method, api, isAsync);
}
return new Promise(function(c, e) {
try {
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (c) c(xhr);
} else {
if (e) e(xhr.statusText + " (" + xhr.status + ")");
}
}
};
xhr.send("");
} catch (ex) {
if (e) e(ex);
}
});
};
/**
* Send an HTTP request to the DBox API and return a Promise that resolves with the response as JSON.
* @param {string} api DBox API
* @param {string} method
* @param {[boolean]} isAsync default: true
* @param {[string]} username
* @param {[string]} pwd
* @returns {Promise <object>} A Promise that resolves with the response as JSON.
*/
dboxXHR.parseJson = function(api, method, isAsync, username, pwd) {
return dboxXHR(api, method, isAsync, username, pwd).then(function(xhr) {
return JSON.parse(xhr.responseText);
});
}
/**
* DBox namespace
* @namespace {DBox}
*/
global.DBox = {
/**
* DBox API namespace
* @namespace {DBox.API}
*/
API: dboxApi,
/**
* @function {DBox.XHR}
*/
xhr: dboxXHR,
};
})(this);

206
shared/html/js/http.js Normal file
View File

@@ -0,0 +1,206 @@
(function(global) {
if (typeof global.Web === "undefined") global.Web = {};
function dataToString(obj) {
if (typeof obj === "string") return obj;
if (typeof obj === "object") return JSON.stringify(obj);
return String(obj);
}
global.Web.Http = {
Request: function() {
var inst = external.Web.Http.createHttpRequest();
this.open = function(method, url) { inst.open(method, url); };
this.getHeaders = function() { return JSON.parse(inst.getHeadersToJson()); };
this.getHeader = function(swName) { try { return inst.getHeader(swName); } catch (e) { return void 0; } };
this.setHeader = function(swName, swValue) { inst.setHeader(swName, swValue); };
this.removeHeader = function(swName) { inst.removeHeader(swName); };
this.clearHeader = function() { inst.clearHeader(); };
this.updateHeaders = function(headers) {
var keys = Object.keys(headers);
this.clearHeader();
for (var i = 0; i < keys.length; i++) {
this.setHeader(keys[i], dataToString(headers[keys[i]]));
}
};
this.setHeaders = function(headers) {
var keys = Object.keys(headers);
for (var i = 0; i < keys.length; i++) {
inst.setHeader(keys[i], dataToString(headers[keys[i]]));
}
};
this.send = function(body, encoding) { return inst.send(body, encoding); };
this.sendAsync = function(body, encoding) {
if (!encoding) encoding = "utf-8";
return new Promise(function(c, e) {
try {
inst.sendAsync(body, encoding, function(resp) {
if (c) c(resp);
});
} catch (ex) { if (e) e(ex); }
});
};
Object.defineProperty(this, "timeout", {
get: function() { return inst.timeout; },
set: function(value) { inst.timeout = value; }
});
Object.defineProperty(this, "readWriteTimeout", {
get: function() { return inst.readWriteTimeout; },
set: function(value) { inst.readWriteTimeout = value; }
});
Object.defineProperty(this, "allowAutoRedirect", {
get: function() { return inst.allowAutoRedirect; },
set: function(value) { inst.allowAutoRedirect = value; }
});
Object.defineProperty(this, "allowWriteStreamBuffering", {
get: function() { return inst.allowWriteStreamBuffering; },
set: function(value) { inst.allowWriteStreamBuffering = value; }
});
Object.defineProperty(this, "keepAlive", {
get: function() { return inst.keepAlive; },
set: function(value) { inst.keepAlive = value; }
});
Object.defineProperty(this, "maximumAutomaticRedirections", {
get: function() { return inst.maximumAutomaticRedirections; },
set: function(value) { inst.maximumAutomaticRedirections = value; }
});
Object.defineProperty(this, "userAgent", {
get: function() { return inst.userAgent; },
set: function(value) { inst.userAgent = value; }
});
Object.defineProperty(this, "referer", {
get: function() { return inst.referer; },
set: function(value) { inst.referer = value; }
});
Object.defineProperty(this, "contentType", {
get: function() { return inst.contentType; },
set: function(value) { inst.contentType = value; }
});
Object.defineProperty(this, "accept", {
get: function() { return inst.accept; },
set: function(value) { inst.accept = value; }
});
Object.defineProperty(this, "proxy", {
get: function() { return inst.proxy; },
set: function(value) { inst.proxy = value; }
});
Object.defineProperty(this, "cookieContainer", {
get: function() { return inst.cookieContainer; },
set: function(value) { inst.cookieContainer = value; }
});
Object.defineProperty(this, "protocolVersion", {
get: function() { return inst.protocolVersion; },
set: function(value) { inst.protocolVersion = value; }
});
Object.defineProperty(this, "preAuthenticate", {
get: function() { return inst.preAuthenticate; },
set: function(value) { inst.preAuthenticate = value; }
});
Object.defineProperty(this, "credentials", {
get: function() { return inst.credentials; },
set: function(value) { inst.credentials = value; }
});
Object.defineProperty(this, "automaticDecompression", {
get: function() { return inst.automaticDecompression; },
set: function(value) { inst.automaticDecompression = value; }
});
this.dispose = function() { inst.dispose(); };
},
Header: {
// 通用头(既可用于请求也可用于响应)
General: {
cacheControl: "Cache-Control",
connection: "Connection",
date: "Date",
pragma: "Pragma",
trailer: "Trailer",
transferEncoding: "Transfer-Encoding",
upgrade: "Upgrade",
via: "Via",
warning: "Warning"
},
// 请求头
Request: {
accept: "Accept",
acceptCharset: "Accept-Charset",
acceptEncoding: "Accept-Encoding",
acceptLanguage: "Accept-Language",
authorization: "Authorization",
expect: "Expect",
from: "From",
host: "Host",
ifMatch: "If-Match",
ifModifiedSince: "If-Modified-Since",
ifNoneMatch: "If-None-Match",
ifRange: "If-Range",
ifUnmodifiedSince: "If-Unmodified-Since",
maxForwards: "Max-Forwards",
proxyAuthorization: "Proxy-Authorization",
range: "Range",
referer: "Referer",
te: "TE",
userAgent: "User-Agent"
},
// 响应头
Response: {
acceptRanges: "Accept-Ranges",
age: "Age",
allow: "Allow",
contentEncoding: "Content-Encoding",
contentLanguage: "Content-Language",
contentLength: "Content-Length",
contentLocation: "Content-Location",
contentRange: "Content-Range",
contentType: "Content-Type",
etag: "ETag",
expires: "Expires",
lastModified: "Last-Modified",
location: "Location",
proxyAuthenticate: "Proxy-Authenticate",
retryAfter: "Retry-After",
server: "Server",
setCookie: "Set-Cookie",
vary: "Vary",
wwwAuthenticate: "WWW-Authenticate"
},
// CORS 相关头(常单独列出,也可归入请求/响应)
Cors: {
accessControlAllowOrigin: "Access-Control-Allow-Origin",
accessControlAllowCredentials: "Access-Control-Allow-Credentials",
accessControlAllowHeaders: "Access-Control-Allow-Headers",
accessControlAllowMethods: "Access-Control-Allow-Methods",
accessControlExposeHeaders: "Access-Control-Expose-Headers",
accessControlMaxAge: "Access-Control-Max-Age",
accessControlRequestHeaders: "Access-Control-Request-Headers",
accessControlRequestMethod: "Access-Control-Request-Method",
origin: "Origin"
},
// 安全/非标准常用头
Security: {
xFrameOptions: "X-Frame-Options",
xContentTypeOptions: "X-Content-Type-Options",
xXssProtection: "X-XSS-Protection",
strictTransportSecurity: "Strict-Transport-Security",
contentSecurityPolicy: "Content-Security-Policy",
referrerPolicy: "Referrer-Policy",
xRequestedWith: "X-Requested-With",
xForwardedFor: "X-Forwarded-For",
xForwardedProto: "X-Forwarded-Proto",
xRealIp: "X-Real-IP"
}
},
Method: {
get: "GET",
post: "POST",
put: "PUT",
delete: "DELETE",
head: "HEAD",
options: "OPTIONS",
trace: "TRACE",
connect: "CONNECT"
},
Version: {
v1_0: "1.0",
v1_1: "1.1",
}
}
})(this);

View File

@@ -162,15 +162,16 @@
}
oldTags.push(k);
promises.push(
anime.runAsync(p.page, [
anime.Keyframes.Opacity.hidden
]).then((function(page, key) {
return function() {
page.style.display = "none";
page.style.opacity = 0;
emit("unload", key);
};
})(p.page, k))
(function(page, key) {
// 返回 anime.runAsync 产生的 Promise
return anime.runAsync(page, [anime.Keyframes.Opacity.hidden])
.then(function() {
page.style.display = "none";
page.style.opacity = 0;
page.style.height = 0;
emit("unload", key);
});
})(p.page, k)
);
p.guide.classList.remove("selected");
}

View File

@@ -52,7 +52,13 @@
},
cancelAll: function() { external.Package.Reader.cancelAll(); },
addApplicationReadItem: function(swItemName) { return external.Package.Reader.addApplicationItem(swItemName); },
removeApplicationReadItem: function(swItemName) { return external.Package.Reader.removeApplicationItem(swItemName); }
removeApplicationReadItem: function(swItemName) { return external.Package.Reader.removeApplicationItem(swItemName); },
updateApplicationReadItems: function(aswArray) {
external.Package.Reader.updateApplicationItems(aswArray);
},
getApplicationReadItems: function() {
return JSON.parse(external.Package.Reader.getApplicationItemsToJson());
},
},
manager: {
add: function(swPkgPath, uOptions) {
@@ -169,5 +175,76 @@
cancelAll: function() { mgr.cancelAll(); },
active: function(swAppUserModelID, swArgs) { return mgr.activeApp(swAppUserModelID, swArgs || null); }
},
utils: (function() {
// 解析包全名,格式: <name>_<version>_<processorArchitecture>_<resourceId>_<publisherId>
// resourceId 可能为空(表现为两个连续下划线)
function parsePackageFullName(fullName) {
var parts = fullName.split('_');
// 按格式应有5部分: [name, version, architecture, resourceId, publisherId]
return {
name: parts[0],
version: parts[1],
architecture: parts[2],
resourceId: parts[3],
publisherId: parts[4]
};
}
// 解析包系列名,格式: <name>_<publisherId>
function parsePackageFamilyName(familyName) {
var underscoreIndex = familyName.indexOf('_');
if (underscoreIndex === -1) {
// 异常情况,按原字符串处理,但题目不会出现
return { name: familyName, publisherId: '' };
}
return {
name: familyName.substring(0, underscoreIndex),
publisherId: familyName.substring(underscoreIndex + 1)
};
}
// 将对象转换为包全名字符串
// 对象应包含 name, version, architecture, publisherId 字段resourceId 可选
function stringifyPackageFullName(pkg) {
var resourcePart = (pkg.resourceId === undefined || pkg.resourceId === null) ? '' : pkg.resourceId;
return pkg.name + '_' + pkg.version + '_' + pkg.architecture + '_' + resourcePart + '_' + pkg.publisherId;
}
// 将对象转换为包系列名字符串
// 对象应包含 name, publisherId 字段
function stringifyPackageFamilyName(pkgFamily) {
return pkgFamily.name + '_' + pkgFamily.publisherId;
}
return {
parsePackageFullName: parsePackageFullName,
parsePackageFamilyName: parsePackageFamilyName,
stringifyPackageFullName: stringifyPackageFullName,
stringifyPackageFamilyName: stringifyPackageFamilyName,
isDependency: function(swPkgIdentityName) {
var list = [
"Microsoft.Net.Native.Framework",
"Microsoft.Net.Native.Runtime",
"Microsoft.Net.CoreRuntime",
"WindowsPreview.Kinect",
"Microsoft.VCLibs",
"Microsoft.WinJS",
"Microsoft.UI.Xaml",
"Microsoft.WindowsAppRuntime",
"Microsoft.Advertising.XAML",
"Microsoft.Midi.Gmdls",
"Microsoft.Services.Store.Engagement",
"Microsoft.Media.PlayReadyClient"
];
for (var i = 0; i < list.length; i++) {
if (swPkgIdentityName.toLowerCase().indexOf(list[i].toLowerCase()) !== -1)
return true;
}
return false;
}
}
})()
};
global.Package.Reader = global.Package.reader;
global.Package.Manager = global.Package.manager;
global.Package.Utils = global.Package.utils;
})(this);

View File

@@ -529,4 +529,102 @@
}
global.Set = Set;
}
})(this);
(function(global) {
// 如果原生 WeakMap 已存在,则不覆盖
if (typeof global.WeakMap !== "undefined") {
return;
}
function WeakMap(iterable) {
// 必须使用 new 调用
if (!(this instanceof WeakMap)) {
throw new TypeError('Constructor WeakMap requires "new"');
}
// 私有存储:每个实例独立维护一个键值对数组
var entries = [];
// 验证 key 必须是对象或函数(不能是 null 或原始值)
function validateKey(key) {
if (key === null || (typeof key !== 'object' && typeof key !== 'function')) {
throw new TypeError('WeakMap key must be an object');
}
}
// 设置键值对
this.set = function(key, value) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
entries[i][1] = value;
return this;
}
}
entries.push([key, value]);
return this;
};
// 获取键对应的值
this.get = function(key) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
return entries[i][1];
}
}
return undefined;
};
// 判断是否存在指定键
this.has = function(key) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
return true;
}
}
return false;
};
// 删除指定键及其值
this.delete = function(key) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
entries.splice(i, 1);
return true;
}
}
return false;
};
// 处理可选的初始化参数iterable例如 [[key1, value1], [key2, value2]]
if (iterable !== null && iterable !== undefined) {
// 支持数组或类数组(具备 forEach 方法的对象)
if (typeof iterable.forEach === 'function') {
var self = this;
iterable.forEach(function(item) {
if (!Array.isArray(item) || item.length < 2) {
throw new TypeError('Iterator value is not an entry object');
}
self.set(item[0], item[1]);
});
} else if (typeof iterable.length === 'number') {
// 类数组对象(如 arguments
for (var i = 0; i < iterable.length; i++) {
var item = iterable[i];
if (!Array.isArray(item) || item.length < 2) {
throw new TypeError('Iterator value is not an entry object');
}
this.set(item[0], item[1]);
}
} else {
throw new TypeError('WeakMap iterable is not iterable');
}
}
}
// 挂载到全局对象
global.WeakMap = WeakMap;
})(this);

View File

@@ -502,6 +502,22 @@
if (!p.removeEventListener) p.removeEventListener = PromisePolyfill.removeEventListener;
if (!p.dispatchEvent) p.dispatchEvent = PromisePolyfill.dispatchEvent;
if (!p.onerror) p.onerror = null;
if (typeof p.prototype.then !== "function") {
p.prototype.then = function(onFulfilled, onRejected) {
return new p(function(resolve, reject) {
this.then(resolve, reject);
}).then(onFulfilled, onRejected);
};
}
if (typeof p.prototype.done !== "function") {
p.prototype.done = function(onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)["catch"](function(ex) {
setTimeout(function() {
throw ex;
}, 0);
});
};
}
}
if (typeof global.WinJS !== "undefined" && typeof global.WinJS.Promise !== "undefined") {
var wp = global.WinJS.Promise;

View File

@@ -25,6 +25,7 @@
});
var themeColor = Bridge.UI.themeColor;
pagemgr.register("reader", document.getElementById("tag-reader"), document.getElementById("page-reader"));
pagemgr.register("acquire", document.getElementById("tag-acquire"), document.getElementById("page-acquire"));
pagemgr.go("reader");
});
})(this);

1357
shared/html/js/storergapi.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -352,6 +352,13 @@ aside>nav ul li div[role=img] {
}
.ispage {
width: 100%;
height: auto;
box-sizing: border-box;
opacity: 1;
transition: all 0.4s cubic-bezier(0.1, 0.9, 0.2, 1);
}
.ispage.padding {
padding: 44px 60px;
}

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>Package Manager</title>
<title>Package Reader</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -32,6 +32,9 @@
<script type="text/javascript" src="libs/msgbox/msgbox.js"></script>
<script type="text/javascript" src="js/init.js"></script>
<script type="text/javascript" src="js/pkginfo.js"></script>
<script type="text/javascript" src="js/http.js"></script>
<script type="text/javascript" src="js/storergapi.js"></script>
<script type="text/javascript" src="js/dboxapi.js"></script>
<script type="text/javascript" src="js/datasrc.js"></script>
<script type="text/javascript" src="js/appbar.js"></script>
<script type="text/javascript" src="js/pagemgr.js"></script>
@@ -55,8 +58,8 @@
<body>
<div id="readerpage" class="pagecontainer full">
<div class="page full guide fold">
<main class="main padding">
<div id="page-reader" style="display: none;" class="ispage">
<main class="main">
<div id="page-reader" style="display: none;" class="ispage padding">
<h2>读取</h2>
<p>请选择一个包,获取其信息。</p>
<div>
@@ -365,6 +368,292 @@
})(this);
</script>
</div>
<div id="page-acquire" style="display: none;" class="ispage padding">
<h2>获取</h2>
<div id="acquire-forbidden" style="width: 100%;">
<p>由于来自 store.rg-adguard.net 的限制,现在暂时无法实现对包的获取。请自行打开下面 URL 进行访问。</p>
<a onclick="external.Process.open ('https://store.rg-adguard.net')">store.rg-adguard.net</a>
</div>
<div id="acquire-enable" style="width: 100%;">
<p>请在下面的输入框中输入要查询的内容,设置好参数后将进行查询。</p>
<div>
<div>
<input type="text" id="acquire-input">
<select id="acquire-valuetype" name="type">
<option value="url">分享链接</option>
<option value="ProductId">产品 ID</option>
<option value="PackageFamilyName">包系列名</option>
<option value="CategoryId">类别 ID</option>
</select>
<select id="acquire-channel" name="ring">
<option title="Windows Insider Fast" value="WIF">快速</option>
<option title="Windows Insider Slow" value="WIS">慢速</option>
<option title="Release Preview" value="RP" selected>发布预览</option>
<option title="Default OS" value="Retail">正式</option>
</select>
</div>
<div>
<div class="itemrow">
<input type="checkbox" id="acquire-smartquery" style="margin-left: 0px;">
<label for="acquire-smartquery">自动查询</label>
</div>
<button id="acquire-query">查询</button>
</div>
</div>
<style>
.acquire-item {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-content: flex-start;
justify-content: flex-start;
width: 100%;
margin: 5px 0;
box-sizing: border-box;
padding: 5px;
border: 1px solid #ccc;
-ms-user-select: element;
user-select: all;
}
.acquire-item #name {
font-size: 11pt;
width: 100%;
line-height: 1.3em;
overflow-x: hidden;
overflow-y: hidden;
text-overflow: ellipsis;
font-weight: normal;
}
.acquire-item .medium {
width: 100%;
font-size: 10pt;
line-height: 1.2em;
font-weight: normal;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: center;
justify-content: space-between;
align-items: center;
margin-top: 3px;
margin-bottom: 3px;
}
.acquire-item .medium div {
text-align: center;
flex: 0 0 auto;
}
.acquire-item .medium #ext {
text-align: left;
max-width: 100px;
/*min-width: 97px;*/
}
.acquire-item .medium div:nth-child(1),
.acquire-item .medium div:nth-child(3),
.acquire-item .medium div:nth-child(5) {
background-color: #e9fff5;
}
.acquire-item .medium div:nth-child(2),
.acquire-item .medium div:nth-child(4),
.acquire-item .medium div:nth-child(6) {
background-color: #ffffef;
}
.acquire-item .bottom {
font-weight: bold;
font-size: 9pt;
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.acquire-item #hashpart {
flex: 1;
overflow-y: hidden;
overflow-x: hidden;
text-overflow: ellipsis;
}
.acquire-item:hover {
background-color: rgba(0, 0, 0, 0.122);
}
.acquire-item #download {
cursor: pointer;
font-size: 10pt;
}
</style>
<div id="acquire-template" class="acquire-item" style="display: none;">
<div id="name" class="top" title="Identity Name">Identity Name</div>
<div class="medium">
<div id="ext" title="File Type">Appx</div>
<div id="version" title="Version">1.0.0.0</div>
<div id="architecture" title="Processor Architecture">neutral</div>
<div id="publisherId" title="Identity Publisher Id"></div>
<div id="size" title="File Size"></div>
</div>
<div class="bottom">
<div id="hashpart"><span>SHA-1: </span><span id="hash"></span></div>
<div><a download id="download" href="">点击下载</a></div>
</div>
</div>
<div id="acquire-loading">
<br>
<div class="container itemrow">
<progress class="win-ring" style="margin-right: 10px;"></progress>
<span class="win-label title" data-res-resxml="MANAGER_APP_INSTALLEDAPPS_LOADING"></span>
<br>
</div>
</div>
<div id="acquire-result" style="width: 100%;">
<p style="font-weight: normal;" title="Category ID"><span>类别 ID</span>: <span id="acquire-categoryid" style="user-select: text; -ms-user-select: element;"></span></p>
<h3>以下可能为检索到的应用</h3>
<div id="acquire-list-app" style="width: 100%;"></div>
<h3>以下可能为检索到的依赖项</h3>
<div id="acquire-list-dep" style="width: 100%;"></div>
</div>
<script>
(function(global) {
var conf = external.Config.current;
var set = conf.getSection("Settings");
var isForbidden = !set.getKey("EnableAcquire").readBool(false);
var acquireInput = document.getElementById("acquire-input");
var acquireValuetype = document.getElementById("acquire-valuetype");
var acquireChannel = document.getElementById("acquire-channel");
var acquireSmartquery = document.getElementById("acquire-smartquery");
var acquireQuery = document.getElementById("acquire-query");
var acquireResult = document.getElementById("acquire-result");
var acquireListApp = document.getElementById("acquire-list-app");
var acquireListDep = document.getElementById("acquire-list-dep");
var acquireForbidden = document.getElementById("acquire-forbidden");
var acquireEnable = document.getElementById("acquire-enable");
var acquireCategoryid = document.getElementById("acquire-categoryid");
var acquireTemplate = document.getElementById("acquire-template");
var acquireLoading = document.getElementById("acquire-loading");
var acquireLoadingRing = acquireLoading.querySelector(".win-ring");
var acquireLoadingLabel = acquireLoading.querySelector(".win-label");
acquireForbidden.style.display = isForbidden ? "" : "none";
acquireEnable.style.display = isForbidden ? "none" : "";
acquireResult.style.display = isForbidden ? "none" : "none";
var dataSrc = {
apps: new DataView.DataSource(),
deps: new DataView.DataSource()
};
var putils = Package.Utils;
var templateFunc = function(item, index) {
var node = acquireTemplate.cloneNode(true);
node.style.display = "";
node.id = "";
var lastDotIndex = item.file.lastIndexOf(".");
var fileNamePart = lastDotIndex !== -1 ? item.file.substring(0, lastDotIndex) : item.file;
var fileExtension = lastDotIndex !== -1 ? item.file.substring(lastDotIndex + 1) : "";
var identityName = putils.parsePackageFullName(fileNamePart);
node.querySelector("#name").textContent = identityName.name;
node.querySelector("#ext").textContent = fileExtension;
node.querySelector("#version").textContent = identityName.version;
node.querySelector("#architecture").textContent = identityName.architecture;
node.querySelector("#publisherId").textContent = identityName.publisherId;
node.querySelector("#size").textContent = item.size;
node.querySelector("#hash").textContent = item.sha1;
node.title = item.file;
node.querySelector("#download").href = item.url;
node.querySelector("#download").title = item.url;
return node;
};
var listView = {
apps: new DataView.ListView(acquireListApp, templateFunc),
deps: new DataView.ListView(acquireListDep, templateFunc)
};
listView.apps.bind(dataSrc.apps);
listView.deps.bind(dataSrc.deps);
var keys = Object.keys(listView);
keys.forEach(function(k, i) {
var listview = listView[k];
var p = document.createElement("p");
p.textContent = "还没有内容...";
listview.emptyView = p;
});
acquireLoading.statusBar = new TransitionPanel(acquireLoading, {
axis: "y",
speed: 500,
});
acquireSmartquery.onchange = function() {
acquireValuetype.disabled =
acquireChannel.disabled = this.checked;
};
acquireSmartquery.checked = set.getKey("AcquireSmartQuery").readBool(false);
acquireValuetype.disabled =
acquireChannel.disabled = acquireSmartquery.checked;
acquireQuery.onclick = function() {
var self = this;
self.disabled = true;
acquireResult.style.display = "none";
acquireLoadingRing.style.display = "";
acquireLoading.statusBar.show();
var queryFunc = StoreRG.xhr.parse;
if (acquireSmartquery.checked) {
queryFunc = StoreRG.xhr.smartQuery;
}
acquireInput.disabled =
acquireSmartquery.disabled =
acquireValuetype.disabled =
acquireChannel.disabled = true;
queryFunc = StoreRG.test;
acquireLoadingLabel.textContent = "正在查询...";
queryFunc(acquireInput.value, acquireValuetype.value, acquireChannel.value).then(function(result) {
acquireCategoryid.textContent = result.categoryId;
var applist = [];
var deplist = [];
result.datas.forEach(function(item, index) {
if (item.file.indexOf(".BlockMap") >= 0) return;
if (putils.isDependency(item.file)) {
deplist.push(item);
} else {
applist.push(item);
}
});
dataSrc.apps.updateList(applist, function(item) {
return item.file;
});
listView.apps.refresh();
dataSrc.deps.updateList(deplist, function(item) {
return item.file;
});
listView.deps.refresh();
acquireLoadingLabel.textContent = "已获取到 {0} 个信息"
.replace("{0}", applist.length + deplist.length);
}, function(err) {
dataSrc.apps.clear();
dataSrc.deps.clear();
listView.apps.refresh();
listView.deps.refresh();
acquireLoadingLabel.textContent = err.message || err;
return Promise.wrap();
}).done(function() {
acquireResult.style.display = "";
setTimeout(function() {
acquireLoading.statusBar.hide();
}, 10000);
self.disabled = false;
acquireLoadingRing.style.display = "none";
acquireInput.disabled =
acquireSmartquery.disabled = false;
acquireValuetype.disabled =
acquireChannel.disabled = acquireSmartquery.checked;
});
}
})(this);
</script>
</div>
</div>
</main>
<aside class="win-ui-dark">
<nav class="container">
@@ -390,6 +679,10 @@
<div role="img">&#57650;</div>
<span class="win-type-base" data-res-resxml="MANAGER_MANAGE"></span>
</li>
<li id="tag-acquire">
<div role="img">&#57624;</div>
<span class="win-type-base" data-res-resxml="MANAGER_MANAGE"></span>
</li>
<li id="tag-settings">
<div role="img">&#57621;</div>
<span class="win-type-base" data-res-resxml="MANAGER_SETTINGS"></span>