mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 04:35:51 +00:00
ci: auto build & obfuscate [2026-04-06 00:50:28] (Jenkins #7)
This commit is contained in:
303
utils/config/ApiProfileManager.js
Normal file
303
utils/config/ApiProfileManager.js
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* ApiProfileManager — API 连接配置组管理
|
||||
*
|
||||
* Profile 是一组完整的 API 连接参数,按模型类型分为三类:
|
||||
* chat — 对话/补全模型(主 API、剧情优化、各子系统等)
|
||||
* embedding — 向量嵌入模型(RAG 向量化)
|
||||
* rerank — 重排序模型(RAG 精排)
|
||||
*
|
||||
* 存储分离:
|
||||
* Profile 元数据(name、type、provider、url、model、params)→ extension_settings.amily2_profiles
|
||||
* API Key → ApiKeyStore(local 或 cloud 加密)
|
||||
*
|
||||
* 功能分配(assignments):
|
||||
* 记录每个系统功能当前使用哪个 Profile ID,存于 extension_settings.amily2_profile_assignments
|
||||
* 选单会按功能对应的 Profile 类型进行过滤,防止类型错配。
|
||||
*
|
||||
* Bus 注册名:'ApiProfiles'
|
||||
*
|
||||
* 公开接口:
|
||||
* getProfiles(type?) — 获取全部或指定类型的 Profile 列表
|
||||
* getProfile(id) — 获取单个 Profile 元数据
|
||||
* createProfile(data) — 新建 Profile(返回新 ID)
|
||||
* updateProfile(id, data) — 更新 Profile 元数据
|
||||
* deleteProfile(id) — 删除 Profile(含清理 Key)
|
||||
* getKey(id) — 读取 Profile 的 API Key(异步,自动解密)
|
||||
* setKey(id, value) — 写入 Profile 的 API Key(异步,自动加密)
|
||||
* getAssignment(slot) — 获取功能槽当前分配的 Profile ID
|
||||
* setAssignment(slot, id) — 设置功能槽的 Profile
|
||||
* getAssignedProfile(slot) — 获取功能槽完整 Profile(含解密 Key)
|
||||
* SLOTS — 可用功能槽清单(静态)
|
||||
* PROFILE_TYPES — Profile 类型定义(静态)
|
||||
*/
|
||||
|
||||
import { extension_settings } from "/scripts/extensions.js";
|
||||
import { saveSettingsDebounced } from "/script.js";
|
||||
import { extensionName } from "../settings.js";
|
||||
import { apiKeyStore } from "./api-key-store/ApiKeyStore.js";
|
||||
|
||||
// ── 类型与功能槽定义 ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Profile 类型定义 */
|
||||
export const PROFILE_TYPES = {
|
||||
chat: {
|
||||
label: '对话模型',
|
||||
icon: 'fa-comments',
|
||||
description: '用于文本生成、对话补全的模型(Chat / Completion)',
|
||||
params: ['maxTokens', 'temperature'],
|
||||
},
|
||||
embedding: {
|
||||
label: '向量嵌入',
|
||||
icon: 'fa-project-diagram',
|
||||
description: '将文本转换为向量的模型,用于 RAG 语义检索',
|
||||
params: ['dimensions', 'encodingFormat'],
|
||||
},
|
||||
rerank: {
|
||||
label: '重排序',
|
||||
icon: 'fa-sort-amount-down',
|
||||
description: '对检索结果重新打分排序的模型,用于 RAG 精排',
|
||||
params: ['topN', 'returnDocuments'],
|
||||
},
|
||||
};
|
||||
|
||||
/** 功能槽:每个系统功能需要的 Profile 类型 */
|
||||
export const SLOTS = {
|
||||
// Chat 槽
|
||||
main: { label: '主 API(正文优化)', type: 'chat' },
|
||||
plotOpt: { label: '剧情优化 / JQYH', type: 'chat' },
|
||||
plotOptConc: { label: '剧情优化(并发)', type: 'chat' },
|
||||
ngms: { label: 'NGMS 历史记录', type: 'chat' },
|
||||
nccs: { label: 'NCCS 并发', type: 'chat' },
|
||||
cwb: { label: '角色世界书', type: 'chat' },
|
||||
autoCharCard: { label: '一键生卡', type: 'chat' },
|
||||
// Embedding 槽
|
||||
ragEmbed: { label: 'RAG 向量化', type: 'embedding' },
|
||||
// Rerank 槽
|
||||
ragRerank: { label: 'RAG 重排序', type: 'rerank' },
|
||||
};
|
||||
|
||||
// extension_settings 存储 key
|
||||
const EXT_PROFILES = 'amily2_profiles';
|
||||
const EXT_ASSIGNMENTS = 'amily2_profile_assignments';
|
||||
|
||||
// ── ApiProfileManager ─────────────────────────────────────────────────────────
|
||||
|
||||
class ApiProfileManager {
|
||||
|
||||
// ── 内部工具 ────────────────────────────────────────────────────────────
|
||||
|
||||
_settings() {
|
||||
if (!extension_settings[extensionName]) extension_settings[extensionName] = {};
|
||||
return extension_settings[extensionName];
|
||||
}
|
||||
|
||||
_profiles() {
|
||||
const s = this._settings();
|
||||
if (!Array.isArray(s[EXT_PROFILES])) s[EXT_PROFILES] = [];
|
||||
return s[EXT_PROFILES];
|
||||
}
|
||||
|
||||
_assignments() {
|
||||
const s = this._settings();
|
||||
if (!s[EXT_ASSIGNMENTS] || typeof s[EXT_ASSIGNMENTS] !== 'object') {
|
||||
s[EXT_ASSIGNMENTS] = {};
|
||||
}
|
||||
return s[EXT_ASSIGNMENTS];
|
||||
}
|
||||
|
||||
_save() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
_newId() {
|
||||
return `p_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
||||
}
|
||||
|
||||
// ── Profile CRUD ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 获取 Profile 列表。
|
||||
* @param {'chat'|'embedding'|'rerank'} [type] 不传则返回全部
|
||||
* @returns {Array}
|
||||
*/
|
||||
getProfiles(type) {
|
||||
const all = this._profiles();
|
||||
return type ? all.filter(p => p.type === type) : [...all];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个 Profile 元数据(不含 Key)。
|
||||
*/
|
||||
getProfile(id) {
|
||||
return this._profiles().find(p => p.id === id) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建 Profile。
|
||||
* @param {Object} data Profile 数据(不含 id、apiKey)
|
||||
* @returns {string} 新 Profile 的 id
|
||||
*/
|
||||
createProfile(data) {
|
||||
const id = this._newId();
|
||||
const profile = this._buildProfile(id, data);
|
||||
this._profiles().push(profile);
|
||||
this._save();
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Profile 元数据(不更新 Key,Key 用 setKey())。
|
||||
*/
|
||||
updateProfile(id, data) {
|
||||
const list = this._profiles();
|
||||
const idx = list.findIndex(p => p.id === id);
|
||||
if (idx === -1) return false;
|
||||
list[idx] = this._buildProfile(id, { ...list[idx], ...data });
|
||||
this._save();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Profile(同时清理存储的 Key 和功能槽引用)。
|
||||
*/
|
||||
deleteProfile(id) {
|
||||
const s = this._settings();
|
||||
s[EXT_PROFILES] = this._profiles().filter(p => p.id !== id);
|
||||
|
||||
// 清理功能槽引用
|
||||
const asgn = this._assignments();
|
||||
for (const slot in asgn) {
|
||||
if (asgn[slot] === id) delete asgn[slot];
|
||||
}
|
||||
|
||||
// 清理 Key
|
||||
apiKeyStore.deleteById(id);
|
||||
|
||||
this._save();
|
||||
}
|
||||
|
||||
// ── Key 操作 ────────────────────────────────────────────────────────────
|
||||
|
||||
/** 读取 Profile 的 API Key(异步,自动解密) */
|
||||
async getKey(id) {
|
||||
return apiKeyStore.retrieveById(id);
|
||||
}
|
||||
|
||||
/** 写入 Profile 的 API Key(异步,自动加密) */
|
||||
async setKey(id, value) {
|
||||
return apiKeyStore.storeById(id, value);
|
||||
}
|
||||
|
||||
// ── 功能槽分配 ──────────────────────────────────────────────────────────
|
||||
|
||||
/** 获取功能槽当前分配的 Profile ID(null = 未分配) */
|
||||
getAssignment(slot) {
|
||||
return this._assignments()[slot] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置功能槽的 Profile。
|
||||
* 会校验 Profile 类型是否与槽类型匹配。
|
||||
*/
|
||||
setAssignment(slot, profileId) {
|
||||
if (!SLOTS[slot]) {
|
||||
console.warn(`[ApiProfiles] 未知功能槽 "${slot}"。`);
|
||||
return false;
|
||||
}
|
||||
if (profileId !== null) {
|
||||
const profile = this.getProfile(profileId);
|
||||
if (!profile) {
|
||||
console.warn(`[ApiProfiles] Profile "${profileId}" 不存在。`);
|
||||
return false;
|
||||
}
|
||||
if (profile.type !== SLOTS[slot].type) {
|
||||
console.warn(`[ApiProfiles] 类型不匹配:槽 "${slot}" 需要 ${SLOTS[slot].type},Profile 类型为 ${profile.type}。`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this._assignments()[slot] = profileId;
|
||||
this._save();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取功能槽完整 Profile,包含解密后的 API Key。
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
async getAssignedProfile(slot) {
|
||||
const id = this.getAssignment(slot);
|
||||
if (!id) return null;
|
||||
const profile = this.getProfile(id);
|
||||
if (!profile) return null;
|
||||
const apiKey = await this.getKey(id);
|
||||
return { ...profile, apiKey };
|
||||
}
|
||||
|
||||
// ── 内部:Profile 对象构造 ──────────────────────────────────────────────
|
||||
|
||||
_buildProfile(id, data) {
|
||||
const type = data.type || 'chat';
|
||||
const base = {
|
||||
id,
|
||||
name: data.name || '未命名配置',
|
||||
type,
|
||||
provider: data.provider || 'openai',
|
||||
apiUrl: data.apiUrl || '',
|
||||
model: data.model || '',
|
||||
};
|
||||
|
||||
if (type === 'chat') {
|
||||
return {
|
||||
...base,
|
||||
maxTokens: data.maxTokens ?? 65500,
|
||||
temperature: data.temperature ?? 1.0,
|
||||
};
|
||||
}
|
||||
if (type === 'embedding') {
|
||||
return {
|
||||
...base,
|
||||
dimensions: data.dimensions ?? null,
|
||||
encodingFormat: data.encodingFormat ?? 'float',
|
||||
};
|
||||
}
|
||||
if (type === 'rerank') {
|
||||
return {
|
||||
...base,
|
||||
topN: data.topN ?? 5,
|
||||
returnDocuments: data.returnDocuments ?? false,
|
||||
};
|
||||
}
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 单例导出 ─────────────────────────────────────────────────────────────────
|
||||
export const apiProfileManager = new ApiProfileManager();
|
||||
|
||||
// ── Bus 注册 ──────────────────────────────────────────────────────────────────
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const _ctx = window.Amily2Bus?.register('ApiProfiles');
|
||||
if (!_ctx) {
|
||||
console.warn('[ApiProfiles] Amily2Bus 尚未就绪,注册跳过。');
|
||||
return;
|
||||
}
|
||||
_ctx.expose({
|
||||
getProfiles: (type) => apiProfileManager.getProfiles(type),
|
||||
getProfile: (id) => apiProfileManager.getProfile(id),
|
||||
createProfile: (data) => apiProfileManager.createProfile(data),
|
||||
updateProfile: (id, data) => apiProfileManager.updateProfile(id, data),
|
||||
deleteProfile: (id) => apiProfileManager.deleteProfile(id),
|
||||
getKey: (id) => apiProfileManager.getKey(id),
|
||||
setKey: (id, val) => apiProfileManager.setKey(id, val),
|
||||
getAssignment: (slot) => apiProfileManager.getAssignment(slot),
|
||||
setAssignment: (slot, id) => apiProfileManager.setAssignment(slot, id),
|
||||
getAssignedProfile: (slot) => apiProfileManager.getAssignedProfile(slot),
|
||||
SLOTS: SLOTS,
|
||||
PROFILE_TYPES: PROFILE_TYPES,
|
||||
});
|
||||
_ctx.log('ApiProfiles', 'info', 'ApiProfiles 服务已注册到 Bus。');
|
||||
} catch (e) {
|
||||
console.error('[ApiProfiles] Bus 注册失败:', e);
|
||||
}
|
||||
}, 0);
|
||||
155
utils/config/ConfigManager.js
Normal file
155
utils/config/ConfigManager.js
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* ConfigManager — 独立配置持久化管理模块
|
||||
*
|
||||
* 解决的安全问题:
|
||||
* SillyTavern 的 extension_settings 会通过 saveSettingsDebounced() 上传到 ST
|
||||
* 服务端 settings.json。使用三方云服务商时,服务商可读取该文件,导致所有
|
||||
* API 密钥泄露。
|
||||
*
|
||||
* 解决方案:
|
||||
* 敏感字段(API Key / URL)→ localStorage(浏览器本地,绝不上传)
|
||||
* 非敏感字段 → extension_settings(维持原有行为)
|
||||
*
|
||||
* Bus 注册名:'Config'
|
||||
*
|
||||
* 公开接口(query('Config')):
|
||||
* get(key) — 读取配置项(自动路由)
|
||||
* set(key, value) — 写入配置项(自动路由 + 触发保存)
|
||||
* getSettings() — 返回完整配置对象(敏感字段从 localStorage 注入)
|
||||
* migrate() — 将 extension_settings 中残留的敏感字段迁移到 localStorage
|
||||
*/
|
||||
|
||||
import { extension_settings } from "/scripts/extensions.js";
|
||||
import { saveSettingsDebounced } from "/script.js";
|
||||
import { extensionName } from "../settings.js";
|
||||
import { SENSITIVE_KEYS } from "./sensitive-keys.js";
|
||||
|
||||
// localStorage key 前缀,避免与其他插件冲突
|
||||
const LS_PREFIX = 'amily2_secure_';
|
||||
|
||||
// ── ConfigManager ────────────────────────────────────────────────────────────
|
||||
|
||||
class ConfigManager {
|
||||
|
||||
/**
|
||||
* 读取配置项。
|
||||
* 敏感字段从 localStorage 读取,其余从 extension_settings 读取。
|
||||
* @param {string} key
|
||||
* @returns {*}
|
||||
*/
|
||||
get(key) {
|
||||
if (SENSITIVE_KEYS.has(key)) {
|
||||
return localStorage.getItem(LS_PREFIX + key) ?? '';
|
||||
}
|
||||
return extension_settings[extensionName]?.[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入配置项并持久化。
|
||||
* 敏感字段写入 localStorage(同时从 extension_settings 清除残留)。
|
||||
* 非敏感字段写入 extension_settings 并触发 saveSettingsDebounced。
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
*/
|
||||
set(key, value) {
|
||||
if (SENSITIVE_KEYS.has(key)) {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
localStorage.setItem(LS_PREFIX + key, value);
|
||||
} else {
|
||||
localStorage.removeItem(LS_PREFIX + key);
|
||||
}
|
||||
// 确保 extension_settings 中不保留该敏感字段
|
||||
const settings = extension_settings[extensionName];
|
||||
if (settings && Object.prototype.hasOwnProperty.call(settings, key)) {
|
||||
delete settings[key];
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
} else {
|
||||
if (!extension_settings[extensionName]) {
|
||||
extension_settings[extensionName] = {};
|
||||
}
|
||||
extension_settings[extensionName][key] = value;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回完整配置对象(合并视图)。
|
||||
* 以 extension_settings 为基础,将 localStorage 中的敏感字段注入覆盖。
|
||||
*
|
||||
* 用途:替换现有 `const settings = extension_settings[extensionName]` 的读取点,
|
||||
* 使 API 调用模块能透明地获取到敏感字段,无需感知存储层差异。
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
getSettings() {
|
||||
const base = extension_settings[extensionName] ?? {};
|
||||
const result = { ...base };
|
||||
for (const key of SENSITIVE_KEYS) {
|
||||
const val = localStorage.getItem(LS_PREFIX + key);
|
||||
// null 表示 localStorage 中不存在,保留 base 中原值(如有)
|
||||
if (val !== null) {
|
||||
result[key] = val;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移:将 extension_settings 中已存在的敏感字段移到 localStorage。
|
||||
*
|
||||
* 应在插件初始化阶段调用一次。
|
||||
* 逻辑:
|
||||
* - 若 extension_settings 有值 → 迁移到 localStorage(若 localStorage 已有值则跳过,保留用户上次输入)
|
||||
* - 从 extension_settings 删除该字段
|
||||
* - 最终触发一次 saveSettingsDebounced 清洗服务端
|
||||
*/
|
||||
migrate() {
|
||||
const settings = extension_settings[extensionName];
|
||||
if (!settings) return;
|
||||
|
||||
let needsSave = false;
|
||||
|
||||
for (const key of SENSITIVE_KEYS) {
|
||||
const settingsVal = settings[key];
|
||||
if (settingsVal !== undefined && settingsVal !== '') {
|
||||
// localStorage 中已有值时不覆盖(优先保留用户最新输入)
|
||||
if (!localStorage.getItem(LS_PREFIX + key)) {
|
||||
localStorage.setItem(LS_PREFIX + key, settingsVal);
|
||||
console.info(`[Amily2-Config] 已迁移敏感字段 "${key}" 到本地安全存储。`);
|
||||
}
|
||||
delete settings[key];
|
||||
needsSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSave) {
|
||||
saveSettingsDebounced();
|
||||
console.info('[Amily2-Config] 敏感配置迁移完成,已从云同步配置中清除密钥。');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 单例导出 ─────────────────────────────────────────────────────────────────
|
||||
export const configManager = new ConfigManager();
|
||||
|
||||
// ── Bus 注册 ──────────────────────────────────────────────────────────────────
|
||||
// setTimeout 确保 window.Amily2Bus 在 Amily2Bus.js 模块体执行后已挂载
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const _ctx = window.Amily2Bus?.register('Config');
|
||||
if (!_ctx) {
|
||||
console.warn('[Config] Amily2Bus 尚未就绪,Config 服务注册跳过。');
|
||||
return;
|
||||
}
|
||||
_ctx.expose({
|
||||
get: (key) => configManager.get(key),
|
||||
set: (key, value) => configManager.set(key, value),
|
||||
getSettings: () => configManager.getSettings(),
|
||||
migrate: () => configManager.migrate(),
|
||||
});
|
||||
_ctx.log('ConfigManager', 'info', 'Config 服务已注册到 Bus。');
|
||||
} catch (e) {
|
||||
console.error('[Config] Bus 注册失败:', e);
|
||||
}
|
||||
}, 0);
|
||||
1
utils/config/api-key-store/ApiKeyStore.js
Normal file
1
utils/config/api-key-store/ApiKeyStore.js
Normal file
File diff suppressed because one or more lines are too long
1
utils/config/api-key-store/crypto-utils.js
Normal file
1
utils/config/api-key-store/crypto-utils.js
Normal file
File diff suppressed because one or more lines are too long
1
utils/config/sensitive-keys.js
Normal file
1
utils/config/sensitive-keys.js
Normal file
@@ -0,0 +1 @@
|
||||
function a0_0x3989(_0x1df389,_0x310d89){_0x1df389=_0x1df389-0x1c6;const _0x5ab531=a0_0x5ab5();let _0x398999=_0x5ab531[_0x1df389];if(a0_0x3989['Foddzv']===undefined){var _0xe81465=function(_0x411c31){const _0x3a91f9='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x38439d='',_0x1a6e14='';for(let _0x57af3d=0x0,_0x377e1b,_0x3132f0,_0x2de46a=0x0;_0x3132f0=_0x411c31['charAt'](_0x2de46a++);~_0x3132f0&&(_0x377e1b=_0x57af3d%0x4?_0x377e1b*0x40+_0x3132f0:_0x3132f0,_0x57af3d++%0x4)?_0x38439d+=String['fromCharCode'](0xff&_0x377e1b>>(-0x2*_0x57af3d&0x6)):0x0){_0x3132f0=_0x3a91f9['indexOf'](_0x3132f0);}for(let _0x1ce259=0x0,_0x4bbf56=_0x38439d['length'];_0x1ce259<_0x4bbf56;_0x1ce259++){_0x1a6e14+='%'+('00'+_0x38439d['charCodeAt'](_0x1ce259)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1a6e14);};const _0x472d1d=function(_0x32e8e6,_0x173be5){let _0x55a167=[],_0x3959cd=0x0,_0x1d7066,_0x2e940d='';_0x32e8e6=_0xe81465(_0x32e8e6);let _0x486e5f;for(_0x486e5f=0x0;_0x486e5f<0x100;_0x486e5f++){_0x55a167[_0x486e5f]=_0x486e5f;}for(_0x486e5f=0x0;_0x486e5f<0x100;_0x486e5f++){_0x3959cd=(_0x3959cd+_0x55a167[_0x486e5f]+_0x173be5['charCodeAt'](_0x486e5f%_0x173be5['length']))%0x100,_0x1d7066=_0x55a167[_0x486e5f],_0x55a167[_0x486e5f]=_0x55a167[_0x3959cd],_0x55a167[_0x3959cd]=_0x1d7066;}_0x486e5f=0x0,_0x3959cd=0x0;for(let _0x432f1f=0x0;_0x432f1f<_0x32e8e6['length'];_0x432f1f++){_0x486e5f=(_0x486e5f+0x1)%0x100,_0x3959cd=(_0x3959cd+_0x55a167[_0x486e5f])%0x100,_0x1d7066=_0x55a167[_0x486e5f],_0x55a167[_0x486e5f]=_0x55a167[_0x3959cd],_0x55a167[_0x3959cd]=_0x1d7066,_0x2e940d+=String['fromCharCode'](_0x32e8e6['charCodeAt'](_0x432f1f)^_0x55a167[(_0x55a167[_0x486e5f]+_0x55a167[_0x3959cd])%0x100]);}return _0x2e940d;};a0_0x3989['Kjmdif']=_0x472d1d,a0_0x3989['QzqDJl']={},a0_0x3989['Foddzv']=!![];}const _0x1734a8=_0x5ab531[0x0],_0xb89448=_0x1df389+_0x1734a8,_0x815408=a0_0x3989['QzqDJl'][_0xb89448];return!_0x815408?(a0_0x3989['nyCSiw']===undefined&&(a0_0x3989['nyCSiw']=!![]),_0x398999=a0_0x3989['Kjmdif'](_0x398999,_0x310d89),a0_0x3989['QzqDJl'][_0xb89448]=_0x398999):_0x398999=_0x815408,_0x398999;}const a0_0x34aa0e=a0_0x3989;(function(_0x442981,_0x44c8a7){const _0x56aab8=a0_0x3989,_0x5ed526=_0x442981();while(!![]){try{const _0x301501=parseInt(_0x56aab8(0x1c7,'vIZB'))/0x1+parseInt(_0x56aab8(0x1d6,'pNw*'))/0x2*(-parseInt(_0x56aab8(0x1dd,'J*IK'))/0x3)+parseInt(_0x56aab8(0x1d3,'zVhL'))/0x4+parseInt(_0x56aab8(0x1d4,'8wEC'))/0x5*(parseInt(_0x56aab8(0x1db,'4pxo'))/0x6)+parseInt(_0x56aab8(0x1cc,'IJnB'))/0x7+-parseInt(_0x56aab8(0x1de,'se7B'))/0x8+-parseInt(_0x56aab8(0x1d5,'1yDA'))/0x9*(-parseInt(_0x56aab8(0x1df,'SJGV'))/0xa);if(_0x301501===_0x44c8a7)break;else _0x5ed526['push'](_0x5ed526['shift']());}catch(_0x3951ea){_0x5ed526['push'](_0x5ed526['shift']());}}}(a0_0x5ab5,0x9f59f));function a0_0x5ab5(){const _0x3ca214=['e8kbFHhdUmofdcniu8ovwq','lSoMWRLta1jiya','aSkTWRn0o8oM','DfhcJ8kiAcrypKddQmoYW7u','W6dcVGSfjfBcPCoUntyqha','WQ4ODuFcKGq9FmouWP1WsW','f17dGbJcMSotmqruu8knESoY','kmoLW6qawq4KwxzZvq1L','W5PtvcvqW7JcISovWRmpWRddMa','sCknW6m6WQhcMmokWPlcImkN','rCkJW60UWQBcUSovgaGFaCk3','W649W7D1WOldHmkFhG','W5lcO8krBHJdPCkYBW','rCkGWRX6W7FdP8oggW','WORcGmonW6fuWRpdPL11ohTU','W7G8WReFBNddT8oGW7HqqK5K','W5hdKsCfWOrmpN4','W5vdorZcT8orWRDWpHldTmokW78','CSkSdIhdKSoksSkG','W6LZxGv3W4BcT8koWRldLW','kmoKW6WbxWTvAhfYsWG','lxhdQKlcLmonBCorimoOWQ4lp2FdPCo+W4zaWPXXWQmgwqa','lCo3fdddU8opwCkObWO','W5yvBYZcJmkiW4BdL8okCwrT','b1GnW6pcPqehkmkulHq','WOBdNtSgW6dcN8ozh8kyW57dSmo/'];a0_0x5ab5=function(){return _0x3ca214;};return a0_0x5ab5();}export const SENSITIVE_KEYS=new Set([a0_0x34aa0e(0x1c6,'B#@t'),a0_0x34aa0e(0x1d9,'NseI'),a0_0x34aa0e(0x1cd,'50TD'),a0_0x34aa0e(0x1da,'pNw*'),a0_0x34aa0e(0x1d7,'^iY*'),a0_0x34aa0e(0x1dc,'6^hr')]);
|
||||
Reference in New Issue
Block a user