mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 10:31:38 +08:00
🎄 Sync 2026-06-20 00:22:22
This commit is contained in:
parent
d92edece61
commit
ba56b2c37b
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-daede
|
||||
PKG_VERSION:=1.14.0
|
||||
PKG_RELEASE:=18
|
||||
PKG_RELEASE:=19
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
|
||||
@ -154,6 +154,10 @@ function findManagedGroup(groups, record) {
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function findGroupByName(groups, name) {
|
||||
return (groups || []).find(function(group) { return group.name === name; }) || null;
|
||||
}
|
||||
|
||||
const api = {
|
||||
deriveAirportName: deriveAirportName,
|
||||
nextPastedName: nextPastedName,
|
||||
@ -167,6 +171,7 @@ const api = {
|
||||
airportSectionValues: airportSectionValues,
|
||||
matchAirport: matchAirport,
|
||||
findManagedGroup: findManagedGroup,
|
||||
findGroupByName: findGroupByName,
|
||||
safeOldNodeIds: safeOldNodeIds
|
||||
};
|
||||
|
||||
|
||||
@ -225,7 +225,8 @@ function isMetadataProxy(node) {
|
||||
if (!name)
|
||||
return false;
|
||||
|
||||
return /^(?:traffic|bandwidth|expire|expiry|expiration|subscription(?:\s+info)?|official\s+website|website)\s*[::|]/i.test(name) ||
|
||||
return /(?:官网|QQ|流量|续费|应急|重置|到期|过期|剩余|套餐)/i.test(name) ||
|
||||
/^(?:traffic|bandwidth|expire|expiry|expiration|subscription(?:\s+info)?|official\s+website|website)\s*[::|]/i.test(name) ||
|
||||
/^(?:(?:剩余|可用|已用|总)?流量|套餐到期|到期时间|到期日|过期时间|有效期|订阅信息)\s*[::|]/i.test(name) ||
|
||||
/^(?:官方网站|官网地址|官方网址|网站地址)\s*[::|]/i.test(name) ||
|
||||
/^(?:加入|联系|客服)?\s*QQ\s*(?:群|交流群|客服|联系)\s*[::|]/i.test(name);
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
'require view.daede.airport-sync as airportSync';
|
||||
'require view.daede.backend as backend';
|
||||
'require view.daede.clash-converter as clashConverter';
|
||||
'require view.daede.daed-session as daedSession';
|
||||
'require view.daede.styles as styles';
|
||||
|
||||
const FETCHER = '/usr/share/luci-app-daede/fetch-clash-yaml.sh';
|
||||
@ -125,16 +126,37 @@ function requestDaedCredentials() {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
ui.showModal(_('Sign in to daed'), [
|
||||
E('p', {}, _('Credentials are used only for this import and are not saved.')),
|
||||
ui.showModal(_('Sign in to daed'), [ E('div', { 'class': 'dd-daed-login' }, [
|
||||
E('p', {}, _('The password is not saved. Sign-in is remembered in this browser for up to 30 days.')),
|
||||
E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Username')), E('div', { 'class': 'cbi-value-field' }, username) ]),
|
||||
E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Password')), E('div', { 'class': 'cbi-value-field' }, password) ]),
|
||||
E('div', { 'class': 'right' }, [ cancel, ' ', login ])
|
||||
]);
|
||||
E('div', { 'class': 'right dd-daed-login-actions' }, [ cancel, ' ', login ])
|
||||
]) ]);
|
||||
username.focus();
|
||||
});
|
||||
}
|
||||
|
||||
function requestDaedToken(endpoint, forceLogin) {
|
||||
const cached = forceLogin ? '' : daedSession.load(window.localStorage);
|
||||
if (cached)
|
||||
return Promise.resolve({ token: cached, cached: true });
|
||||
|
||||
let credentials;
|
||||
return requestDaedCredentials().then(function(value) {
|
||||
credentials = value;
|
||||
return graphQL(endpoint,
|
||||
'query Login($username:String!,$password:String!){token(username:$username,password:$password)}',
|
||||
credentials);
|
||||
}).then(function(login) {
|
||||
daedSession.save(window.localStorage, login.token);
|
||||
return { token: login.token, cached: false };
|
||||
}).finally(function() {
|
||||
if (credentials)
|
||||
credentials.password = '';
|
||||
credentials = null;
|
||||
});
|
||||
}
|
||||
|
||||
function applyUciChanges() {
|
||||
return uci.save().then(function() {
|
||||
return uci.changes().then(function(changes) {
|
||||
@ -227,6 +249,9 @@ return view.extend({
|
||||
const targetSelect = E('select', { 'class': 'dd-conv-target' });
|
||||
const airportName = E('input', { 'class': 'dd-conv-airport-name', 'placeholder': _('Group name'), 'autocomplete': 'off' });
|
||||
const importButton = E('button', { 'class': 'cbi-button cbi-button-positive', 'disabled': 'disabled' }, _('Import node group'));
|
||||
const importTitle = E('h4', { 'class': 'dd-card-title' });
|
||||
const importDescription = E('p', { 'class': 'dd-settings-descr' });
|
||||
const airportNameLabel = E('label');
|
||||
const summary = E('div', { 'class': 'dd-conv-summary' });
|
||||
const groupSummary = E('div', { 'class': 'dd-conv-group-summary' });
|
||||
const sourceStatus = E('div', { 'class': 'dd-conv-status' }, '');
|
||||
@ -271,6 +296,65 @@ return view.extend({
|
||||
});
|
||||
};
|
||||
|
||||
const updateResultSummary = function() {
|
||||
const isDaed = state.target === 'daed';
|
||||
importTitle.textContent = isDaed ? _('3. Import Subscription') : _('3. Import Node Group');
|
||||
importDescription.textContent = isDaed
|
||||
? _('Import selected nodes as one subscription and add it to the default proxy group.')
|
||||
: _('Import the selected nodes as one named group, so large airport node lists stay easy to manage.');
|
||||
airportNameLabel.textContent = isDaed ? _('Subscription name') : _('Group name');
|
||||
const compatible = state.results.filter(function(item) { return item.ok; }).length;
|
||||
const unsupported = state.results.length - compatible;
|
||||
const duplicates = state.results.filter(function(item) { return item.duplicate; }).length;
|
||||
while (summary.firstChild)
|
||||
summary.removeChild(summary.firstChild);
|
||||
[
|
||||
[ _('Detected'), state.results.length + state.ignoredInfo ],
|
||||
[ _('Compatible'), compatible ],
|
||||
[ _('Duplicates'), duplicates ],
|
||||
[ _('Incompatible'), unsupported ],
|
||||
[ _('Ignored info'), state.ignoredInfo ]
|
||||
].forEach(function(item) {
|
||||
summary.appendChild(E('span', { 'class': 'dd-conv-summary-item' }, [
|
||||
E('span', { 'class': 'dd-conv-summary-label' }, item[0]),
|
||||
E('strong', {}, String(item[1]))
|
||||
]));
|
||||
});
|
||||
const count = selectedResults().length;
|
||||
const name = airportName.value.trim();
|
||||
const existing = currentAirport();
|
||||
const validName = airportSync.isGroupNameValid(name, state.target);
|
||||
if (!name) {
|
||||
groupSummary.textContent = isDaed
|
||||
? _('Enter a subscription name to import the selected nodes.')
|
||||
: _('Enter a group name to import the selected nodes.');
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
} else if (!validName) {
|
||||
groupSummary.textContent = _('dae group names may only contain letters, numbers, underscores, and hyphens, and must start with a letter or underscore.');
|
||||
groupSummary.className = 'dd-conv-group-summary err';
|
||||
} else if (isDaed && existing && count === 0) {
|
||||
groupSummary.textContent = _('Subscription "%s" is available on daed. Select nodes above to update it.').format(name);
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
} else if (isDaed) {
|
||||
groupSummary.textContent = existing
|
||||
? _('Update subscription "%s" with %d selected nodes in default group "proxy".').format(name, count)
|
||||
: _('Import %d selected nodes as subscription "%s" into default group "proxy".').format(count, name);
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
} else if (existing && count === 0) {
|
||||
groupSummary.textContent = _('Node group "%s" is available on %s. Select nodes above to update it.').format(name, state.target);
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
} else {
|
||||
groupSummary.textContent = existing
|
||||
? _('Update node group "%s": replace it with %d selected nodes on %s.').format(name, count, state.target)
|
||||
: _('Import %d selected nodes into group "%s" on %s.').format(count, name, state.target);
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
}
|
||||
importButton.textContent = isDaed
|
||||
? (existing ? _('Update subscription (%d)').format(count) : _('Import subscription (%d)').format(count))
|
||||
: (existing ? _('Update node group (%d)').format(count) : _('Import node group (%d)').format(count));
|
||||
importButton.disabled = count === 0 || !validName;
|
||||
};
|
||||
|
||||
const renderResults = function() {
|
||||
while (resultBody.firstChild)
|
||||
resultBody.removeChild(resultBody.firstChild);
|
||||
@ -286,7 +370,7 @@ return view.extend({
|
||||
checkbox.disabled = !item.ok;
|
||||
checkbox.addEventListener('change', function() {
|
||||
item.selected = checkbox.checked;
|
||||
renderResults();
|
||||
updateResultSummary();
|
||||
});
|
||||
|
||||
let resultText, resultClass;
|
||||
@ -313,46 +397,7 @@ return view.extend({
|
||||
if (!shown.length)
|
||||
resultBody.appendChild(E('div', { 'class': 'dd-conv-empty' }, _('No nodes to preview yet. Fetch or paste Clash YAML above.')));
|
||||
|
||||
const compatible = state.results.filter(function(item) { return item.ok; }).length;
|
||||
const unsupported = state.results.length - compatible;
|
||||
const duplicates = state.results.filter(function(item) { return item.duplicate; }).length;
|
||||
while (summary.firstChild)
|
||||
summary.removeChild(summary.firstChild);
|
||||
[
|
||||
[ _('Detected'), state.results.length + state.ignoredInfo ],
|
||||
[ _('Compatible'), compatible ],
|
||||
[ _('Duplicates'), duplicates ],
|
||||
[ _('Incompatible'), unsupported ],
|
||||
[ _('Ignored info'), state.ignoredInfo ]
|
||||
].forEach(function(item) {
|
||||
summary.appendChild(E('span', { 'class': 'dd-conv-summary-item' }, [
|
||||
E('span', { 'class': 'dd-conv-summary-label' }, item[0]),
|
||||
E('strong', {}, String(item[1]))
|
||||
]));
|
||||
});
|
||||
const count = selectedResults().length;
|
||||
const name = airportName.value.trim();
|
||||
const existing = currentAirport();
|
||||
const validName = airportSync.isGroupNameValid(name, state.target);
|
||||
if (!name) {
|
||||
groupSummary.textContent = _('Enter a group name to import the selected nodes.');
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
} else if (!validName) {
|
||||
groupSummary.textContent = _('dae group names may only contain letters, numbers, underscores, and hyphens, and must start with a letter or underscore.');
|
||||
groupSummary.className = 'dd-conv-group-summary err';
|
||||
} else if (existing && count === 0) {
|
||||
groupSummary.textContent = _('Node group "%s" is available on %s. Select nodes above to update it.').format(name, state.target);
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
} else {
|
||||
groupSummary.textContent = existing
|
||||
? _('Update node group "%s": replace it with %d selected nodes on %s.').format(name, count, state.target)
|
||||
: _('Import %d selected nodes into group "%s" on %s.').format(count, name, state.target);
|
||||
groupSummary.className = 'dd-conv-group-summary';
|
||||
}
|
||||
importButton.textContent = existing
|
||||
? _('Update node group (%d)').format(count)
|
||||
: _('Import node group (%d)').format(count);
|
||||
importButton.disabled = count === 0 || !validName;
|
||||
updateResultSummary();
|
||||
};
|
||||
|
||||
const clearPreview = function() {
|
||||
@ -580,8 +625,8 @@ return view.extend({
|
||||
};
|
||||
|
||||
const importDaed = function(items) {
|
||||
let credentials;
|
||||
let token = '';
|
||||
let usedCachedToken = false;
|
||||
const existingAirport = currentAirport();
|
||||
const airportId = existingAirport ? existingAirport.id : airportSync.makeAirportId();
|
||||
const name = airportName.value.trim();
|
||||
@ -603,19 +648,21 @@ return view.extend({
|
||||
const oldNodeIds = existingAirport ? existingAirport.node_ids : [];
|
||||
|
||||
const dropStage = function() { return fs.exec('/bin/rm', [ '-f', stageFile ]).catch(function() {}); };
|
||||
const loadState = function(forceLogin) {
|
||||
return requestDaedToken(endpoint, forceLogin).then(function(auth) {
|
||||
token = auth.token;
|
||||
usedCachedToken = auth.cached;
|
||||
return graphQL(endpoint, 'query State{groups{id name nodes{id} subscriptions{subscription{id}}}}', {}, token);
|
||||
});
|
||||
};
|
||||
|
||||
return fs.write(stageFile, b64).then(function() {
|
||||
return requestDaedCredentials();
|
||||
}).then(function(value) {
|
||||
credentials = value;
|
||||
return graphQL(endpoint,
|
||||
'query Login($username:String!,$password:String!){token(username:$username,password:$password)}',
|
||||
credentials);
|
||||
}).then(function(login) {
|
||||
token = login.token;
|
||||
credentials.password = '';
|
||||
credentials = null;
|
||||
return graphQL(endpoint, 'query State{groups{id name nodes{id} subscriptions{subscription{id}}}}', {}, token);
|
||||
return loadState(false).catch(function(error) {
|
||||
if (!usedCachedToken || !daedSession.isAccessDenied(error))
|
||||
throw error;
|
||||
daedSession.clear(window.localStorage);
|
||||
return loadState(true);
|
||||
});
|
||||
}).then(function(dataValue) {
|
||||
before = dataValue;
|
||||
// drop this airport's previous subscription first: the new one
|
||||
@ -639,44 +686,27 @@ return view.extend({
|
||||
const rows = result.importSubscription.nodeImportResult || [];
|
||||
const failed = rows.filter(function(r) { return !!r.error && r.error !== 'node already exists'; }).length;
|
||||
|
||||
const oldGroup = airportSync.findManagedGroup(before.groups, existingAirport);
|
||||
const ensureGroup = oldGroup
|
||||
? Promise.resolve(oldGroup.id)
|
||||
const oldManagedGroup = airportSync.findManagedGroup(before.groups, existingAirport);
|
||||
const proxyGroup = airportSync.findGroupByName(before.groups, 'proxy');
|
||||
const ensureGroup = proxyGroup
|
||||
? Promise.resolve(proxyGroup.id)
|
||||
: graphQL(endpoint,
|
||||
'mutation CreateGroup($name:String!,$policy:Policy!){createGroup(name:$name,policy:$policy){id}}',
|
||||
{ name: groupName, policy: 'min_moving_avg' }, token).then(function(value) {
|
||||
{ name: 'proxy', policy: 'min_moving_avg' }, token).then(function(value) {
|
||||
createdGroupId = value.createGroup.id;
|
||||
return createdGroupId;
|
||||
});
|
||||
return ensureGroup.then(function(groupId) {
|
||||
const rename = oldGroup && oldGroup.name !== groupName
|
||||
? graphQL(endpoint, 'mutation Rename($id:ID!,$name:String!){renameGroup(id:$id,name:$name)}', { id: groupId, name: groupName }, token)
|
||||
: Promise.resolve();
|
||||
return rename.then(function() {
|
||||
return graphQL(endpoint, 'mutation Pol($id:ID!,$policy:Policy!){groupSetPolicy(id:$id,policy:$policy)}', { id: groupId, policy: 'min_moving_avg' }, token);
|
||||
}).then(function() {
|
||||
// attach the whole subscription to the group
|
||||
return graphQL(endpoint, 'mutation AddSubs($id:ID!,$ids:[ID!]!){groupAddSubscriptions(id:$id,subscriptionIDs:$ids)}', { id: groupId, ids: [ newSubId ] }, token);
|
||||
}).then(function() {
|
||||
return graphQL(endpoint, 'mutation AddSubs($id:ID!,$ids:[ID!]!){groupAddSubscriptions(id:$id,subscriptionIDs:$ids)}', { id: groupId, ids: [ newSubId ] }, token).then(function() {
|
||||
groupReady = true;
|
||||
// scrub the group of its previous members: old manual nodes
|
||||
// (from the legacy importNodes flow) and any other subscription.
|
||||
const oldMemberNodes = oldGroup ? (oldGroup.nodes || []).map(function(n) { return n.id; }) : [];
|
||||
const oldGroupSubs = oldGroup
|
||||
? (oldGroup.subscriptions || []).map(function(s) { return s.subscription && s.subscription.id; }).filter(function(id) { return id && id !== newSubId; })
|
||||
: [];
|
||||
const scrub = [];
|
||||
if (oldMemberNodes.length)
|
||||
scrub.push(graphQL(endpoint, 'mutation DelNodes($id:ID!,$nodeIDs:[ID!]!){groupDelNodes(id:$id,nodeIDs:$nodeIDs)}', { id: groupId, nodeIDs: oldMemberNodes }, token).catch(function() {}));
|
||||
if (oldGroupSubs.length)
|
||||
scrub.push(graphQL(endpoint, 'mutation DelSubs($id:ID!,$ids:[ID!]!){groupDelSubscriptions(id:$id,subscriptionIDs:$ids)}', { id: groupId, ids: oldGroupSubs }, token).catch(function() {}));
|
||||
return Promise.all(scrub).then(function() {
|
||||
// delete this airport's orphaned legacy manual nodes (the old
|
||||
// subscription was already removed before the import).
|
||||
if (!oldNodeIds.length)
|
||||
return null;
|
||||
return graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeNodes(ids:$ids)}', { ids: oldNodeIds }, token).catch(function() {});
|
||||
}).then(function() {
|
||||
const cleanup = [];
|
||||
// Migrate converter-managed airport groups to proxy, but never
|
||||
// disturb proxy's existing nodes or other subscriptions.
|
||||
if (oldManagedGroup && oldManagedGroup.id !== groupId)
|
||||
cleanup.push(graphQL(endpoint, 'mutation RmG($id:ID!){removeGroup(id:$id)}', { id: oldManagedGroup.id }, token).catch(function() {}));
|
||||
if (oldNodeIds.length)
|
||||
cleanup.push(graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeNodes(ids:$ids)}', { ids: oldNodeIds }, token).catch(function() {}));
|
||||
return Promise.all(cleanup).then(function() {
|
||||
writeAirportRecord(existingAirport, {
|
||||
id: airportId,
|
||||
backend: 'daed',
|
||||
@ -694,6 +724,8 @@ return view.extend({
|
||||
});
|
||||
});
|
||||
}).catch(function(error) {
|
||||
if (daedSession.isAccessDenied(error))
|
||||
daedSession.clear(window.localStorage);
|
||||
if (groupReady || !token || (!newSubId && !createdGroupId))
|
||||
throw error;
|
||||
const cleanup = [];
|
||||
@ -703,9 +735,6 @@ return view.extend({
|
||||
cleanup.push(graphQL(endpoint, 'mutation RmG($id:ID!){removeGroup(id:$id)}', { id: createdGroupId }, token));
|
||||
return Promise.all(cleanup).catch(function() {}).then(function() { throw error; });
|
||||
}).finally(function() {
|
||||
if (credentials)
|
||||
credentials.password = '';
|
||||
credentials = null;
|
||||
token = '';
|
||||
return dropStage();
|
||||
});
|
||||
@ -763,10 +792,10 @@ return view.extend({
|
||||
resultBody
|
||||
]),
|
||||
E('div', { 'class': 'dd-card dd-conv-card' }, [
|
||||
E('h4', { 'class': 'dd-card-title' }, _('3. Import Node Group')),
|
||||
E('p', { 'class': 'dd-settings-descr' }, _('Import the selected nodes as one named group, so large airport node lists stay easy to manage.')),
|
||||
importTitle,
|
||||
importDescription,
|
||||
E('div', { 'class': 'dd-conv-import dd-conv-airport' }, [
|
||||
E('label', {}, _('Group name')),
|
||||
airportNameLabel,
|
||||
airportName,
|
||||
E('label', {}, _('Target backend')),
|
||||
targetSelect
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
|
||||
const TOKEN_KEY = 'luci-app-daede.daed-token';
|
||||
|
||||
function clear(storage) {
|
||||
try { storage.removeItem(TOKEN_KEY); } catch (e) {}
|
||||
}
|
||||
|
||||
function tokenExpiry(token) {
|
||||
try {
|
||||
const part = String(token || '').split('.')[1];
|
||||
if (!part)
|
||||
return 0;
|
||||
const normalized = part.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const payload = JSON.parse(atob(normalized));
|
||||
return Number(payload.exp) || 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function load(storage, now) {
|
||||
let token = '';
|
||||
try { token = storage.getItem(TOKEN_KEY) || ''; } catch (e) { return ''; }
|
||||
const current = now == null ? Date.now() / 1000 : Number(now);
|
||||
if (!token || tokenExpiry(token) <= current + 30) {
|
||||
clear(storage);
|
||||
return '';
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function save(storage, token) {
|
||||
try { storage.setItem(TOKEN_KEY, String(token || '')); } catch (e) {}
|
||||
}
|
||||
|
||||
function isAccessDenied(error) {
|
||||
return /access denied|unauthori[sz]ed|invalid token/i.test(String(error && error.message || error || ''));
|
||||
}
|
||||
|
||||
const api = {
|
||||
TOKEN_KEY: TOKEN_KEY,
|
||||
load: load,
|
||||
save: save,
|
||||
clear: clear,
|
||||
isAccessDenied: isAccessDenied
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports)
|
||||
module.exports = api;
|
||||
|
||||
if (typeof baseclass !== 'undefined')
|
||||
return baseclass.extend(api);
|
||||
|
||||
return api;
|
||||
@ -237,7 +237,12 @@ const CSS = [
|
||||
'.dd-conv-submit{justify-content:flex-end}',
|
||||
'.dd-conv-import .cbi-button{margin-left:auto}',
|
||||
'.dd-conv-status{margin-top:9px;font-size:11.5px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;min-height:16px}',
|
||||
'@media(max-width:640px){.dd-conv-card{padding:14px}.dd-conv-heading{align-items:flex-start}.dd-conv-heading .dd-card-title{width:100%}.dd-conv-summary{justify-content:flex-start;width:100%;gap:5px 12px;padding-top:2px}.dd-conv-summary-item{min-width:74px}.dd-conv-filter{margin-left:0;width:100%}.dd-conv-result{grid-template-columns:20px minmax(90px,1fr) 68px}.dd-conv-result-state{grid-column:2/4;font-size:11px}.dd-conv-submit .cbi-button{width:100%!important}.dd-conv-airport{grid-template-columns:82px minmax(0,1fr)}.dd-conv-airport>*{max-width:none !important}.dd-conv-url-row{grid-template-columns:1fr}.dd-conv-url-row input,.dd-conv-url-row .dd-conv-ua,.dd-conv-url-row .cbi-button{width:100%;max-width:none}}'
|
||||
'.dd-daed-login{box-sizing:border-box;width:min(560px,100%);margin:0 auto}',
|
||||
'.dd-daed-login .cbi-value{display:grid!important;grid-template-columns:110px minmax(0,1fr)!important;gap:12px;align-items:center;padding:6px 0!important}',
|
||||
'.dd-daed-login .cbi-value-title,.dd-daed-login .cbi-value-field{box-sizing:border-box;width:auto!important;min-width:0!important;margin:0!important;padding:0!important}',
|
||||
'.dd-daed-login .cbi-value-field input{box-sizing:border-box;width:100%!important;max-width:none!important;min-width:0!important;height:36px!important;margin:0!important;padding:6px 10px!important}',
|
||||
'.dd-daed-login-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:14px}',
|
||||
'@media(max-width:640px){.dd-conv-card{padding:14px}.dd-conv-heading{align-items:flex-start}.dd-conv-heading .dd-card-title{width:100%}.dd-conv-summary{justify-content:flex-start;width:100%;gap:5px 12px;padding-top:2px}.dd-conv-summary-item{min-width:74px}.dd-conv-filter{margin-left:0;width:100%}.dd-conv-result{grid-template-columns:20px minmax(90px,1fr) 68px}.dd-conv-result-state{grid-column:2/4;font-size:11px}.dd-conv-submit .cbi-button{width:100%!important}.dd-conv-airport{grid-template-columns:82px minmax(0,1fr)}.dd-conv-airport>*{max-width:none !important}.dd-conv-url-row{grid-template-columns:1fr}.dd-conv-url-row input,.dd-conv-url-row .dd-conv-ua,.dd-conv-url-row .cbi-button{width:100%;max-width:none}.dd-daed-login .cbi-value{grid-template-columns:1fr!important;gap:4px}.dd-daed-login-actions .cbi-button{flex:1 1 0}}'
|
||||
].join('');
|
||||
|
||||
return baseclass.extend({ CSS: CSS });
|
||||
|
||||
@ -532,7 +532,34 @@ msgid "Sign in to daed"
|
||||
msgstr ""
|
||||
|
||||
#: converter.js
|
||||
msgid "Credentials are used only for this import and are not saved."
|
||||
msgid "The password is not saved. Sign-in is remembered in this browser for up to 30 days."
|
||||
msgstr ""
|
||||
|
||||
msgid "3. Import Subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import selected nodes as one subscription and add it to the default proxy group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscription name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter a subscription name to import the selected nodes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscription \"%s\" is available on daed. Select nodes above to update it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Update subscription \"%s\" with %d selected nodes in default group \"proxy\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "Import %d selected nodes as subscription \"%s\" into default group \"proxy\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "Update subscription (%d)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import subscription (%d)"
|
||||
msgstr ""
|
||||
|
||||
#: converter.js
|
||||
|
||||
@ -764,8 +764,35 @@ msgstr "已存在,跳过"
|
||||
msgid "Sign in to daed"
|
||||
msgstr "登录 daed"
|
||||
|
||||
msgid "Credentials are used only for this import and are not saved."
|
||||
msgstr "凭据仅用于本次导入,不会保存。"
|
||||
msgid "The password is not saved. Sign-in is remembered in this browser for up to 30 days."
|
||||
msgstr "不会保存密码。此浏览器将在 30 天内记住登录状态。"
|
||||
|
||||
msgid "3. Import Subscription"
|
||||
msgstr "3. 导入订阅"
|
||||
|
||||
msgid "Import selected nodes as one subscription and add it to the default proxy group."
|
||||
msgstr "将所选节点作为一条订阅导入,并加入默认 proxy 群组。"
|
||||
|
||||
msgid "Subscription name"
|
||||
msgstr "订阅名称"
|
||||
|
||||
msgid "Enter a subscription name to import the selected nodes."
|
||||
msgstr "请输入订阅名称以导入所选节点。"
|
||||
|
||||
msgid "Subscription \"%s\" is available on daed. Select nodes above to update it."
|
||||
msgstr "daed 中已有订阅“%s”。请在上方选择节点以更新。"
|
||||
|
||||
msgid "Update subscription \"%s\" with %d selected nodes in default group \"proxy\"."
|
||||
msgstr "更新订阅“%s”,使用 %d 个所选节点,并保留在默认 proxy 群组中。"
|
||||
|
||||
msgid "Import %d selected nodes as subscription \"%s\" into default group \"proxy\"."
|
||||
msgstr "将 %d 个所选节点作为订阅“%s”导入默认 proxy 群组。"
|
||||
|
||||
msgid "Update subscription (%d)"
|
||||
msgstr "更新订阅(%d)"
|
||||
|
||||
msgid "Import subscription (%d)"
|
||||
msgstr "导入订阅(%d)"
|
||||
|
||||
msgid "Sign in and import"
|
||||
msgstr "登录并导入"
|
||||
|
||||
@ -764,8 +764,35 @@ msgstr "已存在,跳过"
|
||||
msgid "Sign in to daed"
|
||||
msgstr "登录 daed"
|
||||
|
||||
msgid "Credentials are used only for this import and are not saved."
|
||||
msgstr "凭据仅用于本次导入,不会保存。"
|
||||
msgid "The password is not saved. Sign-in is remembered in this browser for up to 30 days."
|
||||
msgstr "不会保存密码。此浏览器将在 30 天内记住登录状态。"
|
||||
|
||||
msgid "3. Import Subscription"
|
||||
msgstr "3. 导入订阅"
|
||||
|
||||
msgid "Import selected nodes as one subscription and add it to the default proxy group."
|
||||
msgstr "将所选节点作为一条订阅导入,并加入默认 proxy 群组。"
|
||||
|
||||
msgid "Subscription name"
|
||||
msgstr "订阅名称"
|
||||
|
||||
msgid "Enter a subscription name to import the selected nodes."
|
||||
msgstr "请输入订阅名称以导入所选节点。"
|
||||
|
||||
msgid "Subscription \"%s\" is available on daed. Select nodes above to update it."
|
||||
msgstr "daed 中已有订阅“%s”。请在上方选择节点以更新。"
|
||||
|
||||
msgid "Update subscription \"%s\" with %d selected nodes in default group \"proxy\"."
|
||||
msgstr "更新订阅“%s”,使用 %d 个所选节点,并保留在默认 proxy 群组中。"
|
||||
|
||||
msgid "Import %d selected nodes as subscription \"%s\" into default group \"proxy\"."
|
||||
msgstr "将 %d 个所选节点作为订阅“%s”导入默认 proxy 群组。"
|
||||
|
||||
msgid "Update subscription (%d)"
|
||||
msgstr "更新订阅(%d)"
|
||||
|
||||
msgid "Import subscription (%d)"
|
||||
msgstr "导入订阅(%d)"
|
||||
|
||||
msgid "Sign in and import"
|
||||
msgstr "登录并导入"
|
||||
|
||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.6.2
|
||||
PKG_RELEASE:=160
|
||||
PKG_RELEASE:=161
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@ -99,7 +99,9 @@ local function build_common(node)
|
||||
elseif net == "http" then
|
||||
local opts = node["http-opts"]
|
||||
if opts then
|
||||
o.transport.host = get_first(opts.host)
|
||||
-- Clash http-opts carries the camouflage Host under headers.Host
|
||||
-- (a list), like ws-opts; opts.host is not standard for http.
|
||||
o.transport.host = get_first(opts.host) or (opts.headers and get_first(opts.headers.Host))
|
||||
o.transport.path = get_first(opts.path)
|
||||
end
|
||||
|
||||
|
||||
@ -597,7 +597,16 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
elseif result.type == "Xray" and info.net == "tcp" then
|
||||
info.net = "raw"
|
||||
end
|
||||
if info.net == 'h2' or info.net == 'http' then
|
||||
-- Clash 'network: http' = TCP + HTTP/1.1 header obfuscation (no TLS),
|
||||
-- i.e. Xray raw transport with tcp header type "http" -- NOT xhttp
|
||||
-- (splithttp). Normalize to 'raw' + guise 'http' so the tcp_guise
|
||||
-- block below handles it; the server otherwise replies HTTP 400.
|
||||
-- 'h2' still maps to xhttp (Xray dropped the standalone h2 transport).
|
||||
if info.net == 'http' and result.type == "Xray" then
|
||||
info.net = "raw"
|
||||
info.type = "http"
|
||||
result.transport = "raw"
|
||||
elseif info.net == 'h2' or info.net == 'http' then
|
||||
info.net = "http"
|
||||
result.transport = (result.type == "Xray") and "xhttp" or "http"
|
||||
else
|
||||
|
||||
Loading…
Reference in New Issue
Block a user