mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 07:25:13 +08:00
🎉 Sync 2026-03-25 23:51:47
This commit is contained in:
1 parent
096f0f3ba0
commit
91c9ff8e52
4 files changed
+151
-92
No files matched your search
@@ -33,8 +33,8 @@ echo '</style>'
|
|||||||
tail -n +$(( $p +1 )) $DOCNAME.html
|
tail -n +$(( $p +1 )) $DOCNAME.html
|
||||||
} > buildin.html
|
} > buildin.html
|
||||||
popd
|
popd
|
||||||
minify "$PKG_BUILD_DIR"/buildin.html | base64 | tr -d '\n' > "$PKG_BUILD_DIR"/base64
|
minify "$PKG_BUILD_DIR"/buildin.html | gzip -9 -c | 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
|
sed -i "s|'H4sIAAAAAAAAAyvKKS4pyElMTs3Iz0lJLQIA8fIyYQ8AAAA='|'$(cat "$PKG_BUILD_DIR"/base64)'|" "$PKG_BUILD_DIR"/htdocs/luci-static/resources/fchomo.js
|
||||||
# shaka audio
|
# shaka audio
|
||||||
sed -i "s|audio/x-wav|audio/mpeg|;
|
sed -i "s|audio/x-wav|audio/mpeg|;
|
||||||
s|'UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA='|'$(base64 "$CURDIR/docs/$SHARKAUDIO" | tr -d '\n')'|" \
|
s|'UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA='|'$(base64 "$CURDIR/docs/$SHARKAUDIO" | tr -d '\n')'|" \
|
||||||
|
|||||||
@@ -59,9 +59,11 @@ Base64edStr format file content.
|
|||||||
|
|
||||||
Generation steps:
|
Generation steps:
|
||||||
|
|
||||||
1. Base64 encode payload.
|
0. Payload only supports `string` format.
|
||||||
2. Replace all `+` with `-` and all `/` with `_` in base64 string.
|
1. Compress payload using `gzip`. (Optional)
|
||||||
3. Remove all `=` from the EOF the base64 string.
|
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
|
### URIFragment
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
'require validation';
|
'require validation';
|
||||||
|
|
||||||
/* Member */
|
/* Member */
|
||||||
const rulesetdoc = 'data:text/html;base64,' + 'cmxzdHBsYWNlaG9sZGVy';
|
const rulesetdoc = [
|
||||||
|
'data:text/html;base64,',
|
||||||
|
'H4sIAAAAAAAAAyvKKS4pyElMTs3Iz0lJLQIA8fIyYQ8AAAA='
|
||||||
|
];
|
||||||
|
|
||||||
const sharkaudio = function() {
|
const sharkaudio = function() {
|
||||||
return 'data:audio/x-wav;base64,' +
|
return 'data:audio/x-wav;base64,' +
|
||||||
@@ -839,48 +842,95 @@ function calcStringMD5(e) {
|
|||||||
return (p(a) + p(b) + p(c) + p(d)).toLowerCase();
|
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 */
|
/* 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)
|
if (!str)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
/* Thanks to luci-app-ssr-plus */
|
str = base64Prefmt(str);
|
||||||
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
let padding = (4 - (str.length % 4)) % 4;
|
|
||||||
if (padding)
|
|
||||||
str = str + Array(padding + 1).join('=');
|
|
||||||
|
|
||||||
return decodeURIComponent(Array.prototype.map.call(atob(str), (c) =>
|
if (asText)
|
||||||
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
return decodeURIComponent(Array.prototype.map.call(atob(str), c =>
|
||||||
).join(''));
|
'%' + ('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;
|
return null;
|
||||||
|
|
||||||
let buf = encodeURIComponent(str).split('%').slice(1).map(h => parseInt(h, 16));
|
if (typeof input === 'string') {
|
||||||
return btoa(String.fromCharCode(...buf));
|
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) {
|
// thanks to https://github.com/scottschiller/ArmorAlley/blob/master/src/floppy/index-floppy.html
|
||||||
if (!str)
|
/**
|
||||||
|
* 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;
|
return null;
|
||||||
|
|
||||||
/* Thanks to luci-app-ssr-plus */
|
const ds = new DecompressionStream('gzip');
|
||||||
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
let padding = (4 - (str.length % 4)) % 4;
|
|
||||||
if (padding)
|
|
||||||
str = str + Array(padding + 1).join('=');
|
|
||||||
|
|
||||||
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) {
|
const decodedStream = blob_in.stream().pipeThrough(ds);
|
||||||
if (isEmpty(buf))
|
const blob_out = await new window.Response(decodedStream).blob();
|
||||||
return null;
|
|
||||||
|
|
||||||
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) {
|
function generateRand(type, length) {
|
||||||
@@ -916,11 +966,11 @@ function shuffle(StrORArr) {
|
|||||||
else
|
else
|
||||||
throw new Error(`String or Array only`);
|
throw new Error(`String or Array only`);
|
||||||
|
|
||||||
for (let i = arr.length - 1; i > 0; i--) { // Traverse the array from back to front
|
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
|
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')
|
if (typeof StrORArr === 'string')
|
||||||
return arr.join('');
|
return arr.join('');
|
||||||
@@ -951,7 +1001,8 @@ function yaml2json(content, command) {
|
|||||||
|
|
||||||
function isEmpty(res) {
|
function isEmpty(res) {
|
||||||
if (res == null) return true; // null, undefined
|
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 (typeof res === 'object') {
|
||||||
if (res instanceof Map || res instanceof Set) return res.size === 0; // empty Map/Set
|
if (res instanceof Map || res instanceof Set) return res.size === 0; // empty Map/Set
|
||||||
return Object.keys(res).length === 0; // empty Object
|
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'));
|
return _('Expecting: %s').format(_('valid format: 2x, 2p, 4v'));
|
||||||
|
|
||||||
const counts = {};
|
const counts = {};
|
||||||
for (const c of value)
|
for (const c of value)
|
||||||
counts[c] = (counts[c] || 0) + 1;
|
counts[c] = (counts[c] || 0) + 1;
|
||||||
if (!(counts.x === 2 && counts.p === 2 && counts.v === 4))
|
if (!(counts.x === 2 && counts.p === 2 && counts.v === 4))
|
||||||
return _('Expecting: %s').format(_('valid format: 2x, 2p, 4v'));
|
return _('Expecting: %s').format(_('valid format: 2x, 2p, 4v'));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1706,10 +1757,9 @@ return baseclass.extend({
|
|||||||
|
|
||||||
/* Method */
|
/* Method */
|
||||||
calcStringMD5,
|
calcStringMD5,
|
||||||
decodeBase64Str,
|
decodeBase64,
|
||||||
encodeBase64Str,
|
encodeBase64,
|
||||||
decodeBase64Bin,
|
decompressGzip,
|
||||||
encodeBase64Bin,
|
|
||||||
generateRand,
|
generateRand,
|
||||||
shuffle,
|
shuffle,
|
||||||
json2yaml,
|
json2yaml,
|
||||||
|
|||||||
@@ -33,15 +33,24 @@ const parseRulesetYaml = hm.parseYaml.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseRulesetLink(section_type, uri) {
|
async function parseRulesetLink(section_type, uri) {
|
||||||
const filefmt = new RegExp(/^(text|yaml|mrs)$/);
|
const filefmt = /^(text|yaml|mrs)$/;
|
||||||
const filebehav = new RegExp(/^(domain|ipcidr|classical)$/);
|
const filebehav = /^(domain|ipcidr|classical)$/;
|
||||||
|
|
||||||
let config;
|
let config;
|
||||||
|
|
||||||
uri = uri.split('://');
|
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 'http':
|
||||||
case 'https':
|
case 'https':
|
||||||
var url = new URL('http://' + uri[1]);
|
var url = new URL('http://' + uri[1]);
|
||||||
@@ -54,7 +63,7 @@ function parseRulesetLink(section_type, uri) {
|
|||||||
if (filefmt.test(format) && filebehav.test(behavior)) {
|
if (filefmt.test(format) && filebehav.test(behavior)) {
|
||||||
let fullpath = (url.username ? url.username + '@' : '') + url.host + url.pathname + (rawquery ? '?' + decodeURIComponent(rawquery) : '');
|
let fullpath = (url.username ? url.username + '@' : '') + url.host + url.pathname + (rawquery ? '?' + decodeURIComponent(rawquery) : '');
|
||||||
config = {
|
config = {
|
||||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : name ? name : null,
|
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : (name || null),
|
||||||
type: 'http',
|
type: 'http',
|
||||||
format: format,
|
format: format,
|
||||||
behavior: behavior,
|
behavior: behavior,
|
||||||
@@ -64,7 +73,7 @@ function parseRulesetLink(section_type, uri) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
return done(config);
|
||||||
case 'file':
|
case 'file':
|
||||||
var url = new URL('file://' + uri[1]);
|
var url = new URL('file://' + uri[1]);
|
||||||
var format = url.searchParams.get('fmt');
|
var format = url.searchParams.get('fmt');
|
||||||
@@ -75,53 +84,54 @@ function parseRulesetLink(section_type, uri) {
|
|||||||
|
|
||||||
if (filefmt.test(format) && filebehav.test(behavior)) {
|
if (filefmt.test(format) && filebehav.test(behavior)) {
|
||||||
config = {
|
config = {
|
||||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : name ? name : null,
|
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : (name || null),
|
||||||
type: 'file',
|
type: 'file',
|
||||||
format: format,
|
format: format,
|
||||||
behavior: behavior,
|
behavior: behavior,
|
||||||
id: hm.calcStringMD5(String.format('file://%s%s', url.host, url.pathname))
|
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':
|
case 'inline':
|
||||||
var url = new URL('inline:' + uri[1]);
|
var url = new URL('inline:' + uri[1]);
|
||||||
var behavior = url.searchParams.get('behav');
|
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 = {
|
config = {
|
||||||
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : null,
|
label: url.hash ? decodeURIComponent(url.hash.slice(1)) : null,
|
||||||
type: 'inline',
|
type: 'inline',
|
||||||
behavior: behavior,
|
behavior: behavior,
|
||||||
payload: payload,
|
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({
|
return view.extend({
|
||||||
load() {
|
load() {
|
||||||
return Promise.all([
|
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) {
|
render(data) {
|
||||||
|
const rulesetdoc = data[1];
|
||||||
|
|
||||||
let m, s, o;
|
let m, s, o;
|
||||||
|
|
||||||
m = new form.Map('fchomo', _('Edit ruleset'));
|
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>')
|
_('Supports rule-set links of type: <code>%s</code> and format: <code>%s</code>.</br>')
|
||||||
.format('file, http, inline', 'text, yaml, mrs') +
|
.format('file, http, inline', 'text, yaml, mrs') +
|
||||||
_('Please refer to <a href="%s" target="_blank">%s</a> for link format standards.')
|
_('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' +
|
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' +
|
'file:///example.txt?fmt=text&behav=domain&fill=LmNuCg#CN%20TLD\n' +
|
||||||
'inline://LSAnLmhrJwoK?behav=domain#HK%20TLD\n';
|
'inline://LSAnLmhrJwoK?behav=domain#HK%20TLD\n' +
|
||||||
o.handleFn = function(textarea) {
|
'inline://H4sIAAAAAAACA9NVUNcrKVcHANszKpEHAAAA?behav=domain#TW%20TLD\n';
|
||||||
|
o.handleFn = async function(textarea) {
|
||||||
let input_links = textarea.getValue().trim().split('\n');
|
let input_links = textarea.getValue().trim().split('\n');
|
||||||
|
if (!input_links[0]) return ui.hideModal();
|
||||||
|
|
||||||
let imported_count = 0;
|
let imported_count = 0;
|
||||||
|
input_links = [...new Set(input_links)]; // Remove duplicate lines
|
||||||
if (input_links && input_links[0]) {
|
for (const link of input_links) {
|
||||||
/* Remove duplicate lines */
|
let config = await parseRulesetLink(section_type, link);
|
||||||
input_links = input_links.reduce((pre, cur) =>
|
if (config) {
|
||||||
(!pre.includes(cur) && pre.push(cur), pre), []);
|
this.write(config);
|
||||||
|
imported_count++;
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
if (imported_count)
|
||||||
return this.save();
|
return this.save();
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in new issue
Block a user