mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 10:31:38 +08:00
🎉 Sync 2026-03-25 23:51:47
This commit is contained in:
parent
f9454edb7a
commit
a0b1837a1a
@ -33,8 +33,8 @@ echo '</style>'
|
||||
tail -n +$(( $p +1 )) $DOCNAME.html
|
||||
} > buildin.html
|
||||
popd
|
||||
minify "$PKG_BUILD_DIR"/buildin.html | base64 | tr -d '\n' > "$PKG_BUILD_DIR"/base64
|
||||
sed -i "s|'cmxzdHBsYWNlaG9sZGVy'|'$(cat "$PKG_BUILD_DIR"/base64)'|" "$PKG_BUILD_DIR"/htdocs/luci-static/resources/fchomo.js
|
||||
minify "$PKG_BUILD_DIR"/buildin.html | gzip -9 -c | base64 | tr -d '\n' > "$PKG_BUILD_DIR"/base64
|
||||
sed -i "s|'H4sIAAAAAAAAAyvKKS4pyElMTs3Iz0lJLQIA8fIyYQ8AAAA='|'$(cat "$PKG_BUILD_DIR"/base64)'|" "$PKG_BUILD_DIR"/htdocs/luci-static/resources/fchomo.js
|
||||
# shaka audio
|
||||
sed -i "s|audio/x-wav|audio/mpeg|;
|
||||
s|'UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA='|'$(base64 "$CURDIR/docs/$SHARKAUDIO" | tr -d '\n')'|" \
|
||||
|
||||
@ -59,9 +59,11 @@ Base64edStr format file content.
|
||||
|
||||
Generation steps:
|
||||
|
||||
1. Base64 encode payload.
|
||||
2. Replace all `+` with `-` and all `/` with `_` in base64 string.
|
||||
3. Remove all `=` from the EOF the base64 string.
|
||||
0. Payload only supports `string` format.
|
||||
1. Compress payload using `gzip`. (Optional)
|
||||
2. Base64 encode payload/compressed payload.
|
||||
3. Replace all `+` with `-` and all `/` with `_` in base64 string.
|
||||
4. Remove all `=` from the EOF the base64 string.
|
||||
|
||||
### URIFragment
|
||||
|
||||
|
||||
@ -8,7 +8,10 @@
|
||||
'require validation';
|
||||
|
||||
/* Member */
|
||||
const rulesetdoc = 'data:text/html;base64,' + 'cmxzdHBsYWNlaG9sZGVy';
|
||||
const rulesetdoc = [
|
||||
'data:text/html;base64,',
|
||||
'H4sIAAAAAAAAAyvKKS4pyElMTs3Iz0lJLQIA8fIyYQ8AAAA='
|
||||
];
|
||||
|
||||
const sharkaudio = function() {
|
||||
return 'data:audio/x-wav;base64,' +
|
||||
@ -839,48 +842,95 @@ function calcStringMD5(e) {
|
||||
return (p(a) + p(b) + p(c) + p(d)).toLowerCase();
|
||||
}
|
||||
|
||||
/* Thanks to luci-app-ssr-plus */
|
||||
function base64Prefmt(str) {
|
||||
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const padding = (4 - (str.length % 4)) % 4;
|
||||
if (padding)
|
||||
str += '='.repeat(padding);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/* thanks to homeproxy */
|
||||
function decodeBase64Str(str) {
|
||||
/**
|
||||
* General Base64 Decoding
|
||||
* @param {string} str - Base64 string
|
||||
* @param {boolean} asText - true: decoded string (UTF-8), false: Uint8Array
|
||||
* @returns {string|Uint8Array}
|
||||
*/
|
||||
function decodeBase64(str, asText = false) {
|
||||
if (!str)
|
||||
return null;
|
||||
|
||||
/* Thanks to luci-app-ssr-plus */
|
||||
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
let padding = (4 - (str.length % 4)) % 4;
|
||||
if (padding)
|
||||
str = str + Array(padding + 1).join('=');
|
||||
str = base64Prefmt(str);
|
||||
|
||||
return decodeURIComponent(Array.prototype.map.call(atob(str), (c) =>
|
||||
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
||||
).join(''));
|
||||
if (asText)
|
||||
return decodeURIComponent(Array.prototype.map.call(atob(str), c =>
|
||||
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
||||
).join(''));
|
||||
|
||||
return Uint8Array.fromBase64(str); // OR Uint8Array.from(atob(str), c => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
function encodeBase64Str(str) {
|
||||
if (!str)
|
||||
/**
|
||||
* General Base64 Encoding
|
||||
* @param {string|Array|ArrayBuffer|Uint8Array} input - String or binary data to be encoded
|
||||
* @returns {string}
|
||||
*/
|
||||
function encodeBase64(input) {
|
||||
if (isEmpty(input))
|
||||
return null;
|
||||
|
||||
let buf = encodeURIComponent(str).split('%').slice(1).map(h => parseInt(h, 16));
|
||||
return btoa(String.fromCharCode(...buf));
|
||||
if (typeof input === 'string') {
|
||||
const rawStr = encodeURIComponent(input).replace(/%([0-9A-F]{2})/g, (match, p1) =>
|
||||
String.fromCharCode(parseInt(p1, 16))
|
||||
);
|
||||
return btoa(rawStr);
|
||||
}
|
||||
|
||||
const buf = (input instanceof ArrayBuffer) ? new Uint8Array(input) : input;
|
||||
return new Uint8Array(buf).toBase64(); // OR btoa(String.fromCharCode.apply(null, buf));
|
||||
}
|
||||
|
||||
function decodeBase64Bin(str) {
|
||||
if (!str)
|
||||
// thanks to https://github.com/scottschiller/ArmorAlley/blob/master/src/floppy/index-floppy.html
|
||||
/**
|
||||
* Unzip Gzip data
|
||||
* @param {string|Array|ArrayBuffer|Uint8Array} input - Input data (Base64 string or binary object)
|
||||
* @param {boolean} asText - Return a string? (true: text, false: arrayBuffer)
|
||||
* @returns {Promise<string|Uint8Array>}
|
||||
*/
|
||||
async function decompressGzip(input, asText = false) {
|
||||
if (isEmpty(input))
|
||||
return null;
|
||||
|
||||
/* Thanks to luci-app-ssr-plus */
|
||||
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
let padding = (4 - (str.length % 4)) % 4;
|
||||
if (padding)
|
||||
str = str + Array(padding + 1).join('=');
|
||||
const ds = new DecompressionStream('gzip');
|
||||
|
||||
return Array.prototype.map.call(atob(str), c => c.charCodeAt(0)); // OR Uint8Array.fromBase64(str);
|
||||
}
|
||||
let blob_in;
|
||||
if (typeof input === 'string') {
|
||||
if (input.match(/^H4sI/)) { // Gzip magic + Deflate
|
||||
const response = await window.fetch('data:application/octet-stream;base64,' + base64Prefmt(input));
|
||||
blob_in = await response.blob();
|
||||
} else
|
||||
throw new Error('Not a valid base64 encoded gzip');
|
||||
} else {
|
||||
// The `input` should be `Array` or `ArrayBuffer` or `Uint8Array`.
|
||||
input = new Uint8Array(input);
|
||||
if (input.subarray(0, 3).join(',') === [0x1f, 0x8b, 0x08].join(',')) // Gzip magic + Deflate
|
||||
blob_in = new Blob([input]);
|
||||
else
|
||||
throw new Error('Not a valid gzip');
|
||||
}
|
||||
|
||||
function encodeBase64Bin(buf) {
|
||||
if (isEmpty(buf))
|
||||
return null;
|
||||
const decodedStream = blob_in.stream().pipeThrough(ds);
|
||||
const blob_out = await new window.Response(decodedStream).blob();
|
||||
|
||||
return btoa(String.fromCharCode(...buf)); // OR new Uint8Array(buf).toBase64();
|
||||
if (asText)
|
||||
return await blob_out.text();
|
||||
else {
|
||||
const buf = await blob_out.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
}
|
||||
|
||||
function generateRand(type, length) {
|
||||
@ -916,11 +966,11 @@ function shuffle(StrORArr) {
|
||||
else
|
||||
throw new Error(`String or Array only`);
|
||||
|
||||
for (let i = arr.length - 1; i > 0; i--) { // Traverse the array from back to front
|
||||
const j = Math.floor(Math.random() * (i + 1)); // Generate a random index between 0 and i
|
||||
for (let i = arr.length - 1; i > 0; i--) { // Traverse the array from back to front
|
||||
const j = Math.floor(Math.random() * (i + 1)); // Generate a random index between 0 and i
|
||||
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]]; // Swap positions
|
||||
}
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]]; // Swap positions
|
||||
}
|
||||
|
||||
if (typeof StrORArr === 'string')
|
||||
return arr.join('');
|
||||
@ -951,7 +1001,8 @@ function yaml2json(content, command) {
|
||||
|
||||
function isEmpty(res) {
|
||||
if (res == null) return true; // null, undefined
|
||||
if (typeof res === 'string' || Array.isArray(res)) return res.length === 0; // empty String/Array
|
||||
if (typeof res.length === 'number') return res.length === 0; // empty String/Array/TypedArray
|
||||
if (res instanceof ArrayBuffer) return res.byteLength === 0; // empty ArrayBuffer
|
||||
if (typeof res === 'object') {
|
||||
if (res instanceof Map || res instanceof Set) return res.size === 0; // empty Map/Set
|
||||
return Object.keys(res).length === 0; // empty Object
|
||||
@ -1510,9 +1561,9 @@ function validateSudokuCustomTable(section_id, value) {
|
||||
return _('Expecting: %s').format(_('valid format: 2x, 2p, 4v'));
|
||||
|
||||
const counts = {};
|
||||
for (const c of value)
|
||||
counts[c] = (counts[c] || 0) + 1;
|
||||
if (!(counts.x === 2 && counts.p === 2 && counts.v === 4))
|
||||
for (const c of value)
|
||||
counts[c] = (counts[c] || 0) + 1;
|
||||
if (!(counts.x === 2 && counts.p === 2 && counts.v === 4))
|
||||
return _('Expecting: %s').format(_('valid format: 2x, 2p, 4v'));
|
||||
|
||||
return true;
|
||||
@ -1706,10 +1757,9 @@ return baseclass.extend({
|
||||
|
||||
/* Method */
|
||||
calcStringMD5,
|
||||
decodeBase64Str,
|
||||
encodeBase64Str,
|
||||
decodeBase64Bin,
|
||||
encodeBase64Bin,
|
||||
decodeBase64,
|
||||
encodeBase64,
|
||||
decompressGzip,
|
||||
generateRand,
|
||||
shuffle,
|
||||
json2yaml,
|
||||
|
||||
@ -33,15 +33,24 @@ const parseRulesetYaml = hm.parseYaml.extend({
|
||||
}
|
||||
});
|
||||
|
||||
function parseRulesetLink(section_type, uri) {
|
||||
const filefmt = new RegExp(/^(text|yaml|mrs)$/);
|
||||
const filebehav = new RegExp(/^(domain|ipcidr|classical)$/);
|
||||
async function parseRulesetLink(section_type, uri) {
|
||||
const filefmt = /^(text|yaml|mrs)$/;
|
||||
const filebehav = /^(domain|ipcidr|classical)$/;
|
||||
|
||||
let config;
|
||||
|
||||
uri = uri.split('://');
|
||||
if (uri[0] && uri[1]) {
|
||||
switch (uri[0]) {
|
||||
|
||||
const done = (conf) => {
|
||||
if (conf && conf.type && conf.id) {
|
||||
if (!conf.label) conf.label = conf.id;
|
||||
return conf;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!(uri[0] && uri[1])) return done();
|
||||
|
||||
switch (uri[0]) {
|
||||
case 'http':
|
||||
case 'https':
|
||||
var url = new URL('http://' + uri[1]);
|
||||
@ -54,7 +63,7 @@ function parseRulesetLink(section_type, uri) {
|
||||
if (filefmt.test(format) && filebehav.test(behavior)) {
|
||||
let fullpath = (url.username ? url.username + '@' : '') + url.host + url.pathname + (rawquery ? '?' + decodeURIComponent(rawquery) : '');
|
||||
config = {
|
||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : name ? name : null,
|
||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : (name || null),
|
||||
type: 'http',
|
||||
format: format,
|
||||
behavior: behavior,
|
||||
@ -64,7 +73,7 @@ function parseRulesetLink(section_type, uri) {
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
return done(config);
|
||||
case 'file':
|
||||
var url = new URL('file://' + uri[1]);
|
||||
var format = url.searchParams.get('fmt');
|
||||
@ -75,53 +84,54 @@ function parseRulesetLink(section_type, uri) {
|
||||
|
||||
if (filefmt.test(format) && filebehav.test(behavior)) {
|
||||
config = {
|
||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : name ? name : null,
|
||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : (name || null),
|
||||
type: 'file',
|
||||
format: format,
|
||||
behavior: behavior,
|
||||
id: hm.calcStringMD5(String.format('file://%s%s', url.host, url.pathname))
|
||||
};
|
||||
hm.writeFile(section_type, config.id, hm.decodeBase64Str(filler));
|
||||
if (filler?.match(/^H4sI/)) // Gzip magic + Deflate
|
||||
await hm.writeFile(section_type, config.id, await hm.decompressGzip(filler, true));
|
||||
else
|
||||
hm.writeFile(section_type, config.id, hm.decodeBase64(filler, true));
|
||||
}
|
||||
|
||||
break;
|
||||
return done(config);
|
||||
case 'inline':
|
||||
var url = new URL('inline:' + uri[1]);
|
||||
var behavior = url.searchParams.get('behav');
|
||||
var payload = hm.decodeBase64Str(url.pathname).trim();
|
||||
var payload = url.pathname.match(/^H4sI/) // Gzip magic + Deflate
|
||||
? await hm.decompressGzip(url.pathname, true)
|
||||
: hm.decodeBase64(url.pathname, true);
|
||||
|
||||
if (filebehav.test(behavior) && payload && payload.length) {
|
||||
payload = (payload || '').trim();
|
||||
if (filebehav.test(behavior) && payload) {
|
||||
config = {
|
||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : null,
|
||||
type: 'inline',
|
||||
behavior: behavior,
|
||||
payload: payload,
|
||||
id: hm.calcStringMD5(String.format('inline:%s', btoa(payload)))
|
||||
id: hm.calcStringMD5(String.format('inline:%s', hm.encodeBase64(payload)))
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
return done(config);
|
||||
default:
|
||||
return done();
|
||||
}
|
||||
|
||||
if (config) {
|
||||
if (!config.type || !config.id)
|
||||
return null;
|
||||
else if (!config.label)
|
||||
config.label = config.id;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
uci.load('fchomo')
|
||||
uci.load('fchomo'),
|
||||
hm.decompressGzip(hm.rulesetdoc[1], true).then((res) => { return hm.rulesetdoc[0] + hm.encodeBase64(res); })
|
||||
]);
|
||||
},
|
||||
|
||||
render(data) {
|
||||
const rulesetdoc = data[1];
|
||||
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('fchomo', _('Edit ruleset'));
|
||||
@ -188,34 +198,31 @@ return view.extend({
|
||||
_('Supports rule-set links of type: <code>%s</code> and format: <code>%s</code>.</br>')
|
||||
.format('file, http, inline', 'text, yaml, mrs') +
|
||||
_('Please refer to <a href="%s" target="_blank">%s</a> for link format standards.')
|
||||
.format(hm.rulesetdoc, _('Ruleset-URI-Scheme')));
|
||||
.format(rulesetdoc, _('Ruleset-URI-Scheme')));
|
||||
o.placeholder = 'http(s)://github.com/ACL4SSR/ACL4SSR/raw/refs/heads/master/Clash/Providers/BanAD.yaml?fmt=yaml&behav=classical&rawq=good%3Djob#BanAD\n' +
|
||||
'file:///example.txt?fmt=text&behav=domain&fill=LmNuCg#CN%20TLD\n' +
|
||||
'inline://LSAnLmhrJwoK?behav=domain#HK%20TLD\n';
|
||||
o.handleFn = function(textarea) {
|
||||
'inline://LSAnLmhrJwoK?behav=domain#HK%20TLD\n' +
|
||||
'inline://H4sIAAAAAAACA9NVUNcrKVcHANszKpEHAAAA?behav=domain#TW%20TLD\n';
|
||||
o.handleFn = async function(textarea) {
|
||||
let input_links = textarea.getValue().trim().split('\n');
|
||||
if (!input_links[0]) return ui.hideModal();
|
||||
|
||||
let imported_count = 0;
|
||||
|
||||
if (input_links && input_links[0]) {
|
||||
/* Remove duplicate lines */
|
||||
input_links = input_links.reduce((pre, cur) =>
|
||||
(!pre.includes(cur) && pre.push(cur), pre), []);
|
||||
|
||||
input_links.forEach((l) => {
|
||||
let config = parseRulesetLink(section_type, l);
|
||||
if (config) {
|
||||
this.write(config);
|
||||
imported_count++;
|
||||
}
|
||||
});
|
||||
|
||||
if (imported_count === 0)
|
||||
ui.addNotification(null, E('p', _('No valid rule-set link found.')));
|
||||
else
|
||||
ui.addNotification(null, E('p', _('Successfully imported %s %s of total %s.')
|
||||
.format(imported_count, _('rule-set'), input_links.length)), 'info');
|
||||
input_links = [...new Set(input_links)]; // Remove duplicate lines
|
||||
for (const link of input_links) {
|
||||
let config = await parseRulesetLink(section_type, link);
|
||||
if (config) {
|
||||
this.write(config);
|
||||
imported_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (imported_count === 0)
|
||||
ui.addNotification(null, E('p', _('No valid rule-set link found.')));
|
||||
else
|
||||
ui.addNotification(null, E('p', _('Successfully imported %s %s of total %s.')
|
||||
.format(imported_count, _('rule-set'), input_links.length)), 'info');
|
||||
|
||||
if (imported_count)
|
||||
return this.save();
|
||||
else
|
||||
|
||||
Loading…
Reference in New Issue
Block a user