Import source: src/state
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
+218
@@ -0,0 +1,218 @@
|
|||||||
|
import type { ChatSession, GroupMessage, BucketEntry, LogEntry, ModelInfo, TgUser, ExecutionContext } from './types';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let cfg: any = null as any;
|
||||||
|
let API_URL = "";
|
||||||
|
let BOT_INFO: TgUser | null = null;
|
||||||
|
let BOT_SELF_ID: number | null = null;
|
||||||
|
let isInitialized = false;
|
||||||
|
let maintenanceCache: { value: boolean; ts: number } | null = null;
|
||||||
|
let requestOrigin = "";
|
||||||
|
let globalCtx: ExecutionContext | null = null;
|
||||||
|
|
||||||
|
const runtimeStatus = {
|
||||||
|
startedAt: Date.now(),
|
||||||
|
scheduler: {
|
||||||
|
running: false,
|
||||||
|
lastRunAt: null as number | null,
|
||||||
|
lastSuccessAt: null as number | null,
|
||||||
|
lastErrorAt: null as number | null,
|
||||||
|
lastError: null as string | null,
|
||||||
|
},
|
||||||
|
polling: {
|
||||||
|
running: false,
|
||||||
|
lastPollAt: null as number | null,
|
||||||
|
lastUpdateAt: null as number | null,
|
||||||
|
lastUpdateId: null as number | null,
|
||||||
|
lastErrorAt: null as number | null,
|
||||||
|
lastError: null as string | null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeRequests = new Map<number, Set<{ id: string; ts: number }>>();
|
||||||
|
const callbackRateLimits = new Map<number, number[]>();
|
||||||
|
const sessionLoadLocks = new Map<number, Promise<ChatSession>>();
|
||||||
|
const groupContextCache = new Map<number, { messages: GroupMessage[]; lastCleanup: number }>();
|
||||||
|
const modelListStates = new Map<string, { page: number; perPage: number; totalPages: number }>();
|
||||||
|
const adminPanelStates = new Map<number, { page: number; perPage: number; sortBy: "new" | "active" | "messages" }>();
|
||||||
|
const broadcastStates = new Map<number, { mode: "all" | "vip" | "free" | "specific"; userId?: number }>();
|
||||||
|
|
||||||
|
let globalDisabledKeys: Record<string, number> = {};
|
||||||
|
let _saveDisabledKeysTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let lastDisabledKeysFetch = 0;
|
||||||
|
|
||||||
|
const activeProgressMessages = new Map<number, number>();
|
||||||
|
const lastTypingSent = new Map<number, number>();
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// SECTION: LOGGER
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
const MAX_LOGS = 100;
|
||||||
|
const recentLogs: LogEntry[] = [];
|
||||||
|
|
||||||
|
function log(level: LogEntry["level"], message: string, context?: unknown) {
|
||||||
|
if (level === "info") {
|
||||||
|
const entry: LogEntry = { timestamp: Date.now(), level, message, context };
|
||||||
|
recentLogs.push(entry);
|
||||||
|
if (recentLogs.length > MAX_LOGS) recentLogs.shift();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry: LogEntry = { timestamp: Date.now(), level, message, context };
|
||||||
|
const fn = level === "error" ? console.error : console.warn;
|
||||||
|
fn(`[${level.toUpperCase()}] ${message}`, context ?? "");
|
||||||
|
recentLogs.push(entry);
|
||||||
|
if (recentLogs.length > MAX_LOGS) recentLogs.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = {
|
||||||
|
info: (msg: string, ctx?: unknown) => log("info", msg, ctx),
|
||||||
|
warn: (msg: string, ctx?: unknown) => log("warn", msg, ctx),
|
||||||
|
error: (msg: string, ctx?: unknown) => log("error", msg, ctx),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// SECTION: CACHE (LRU eviction)
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
class CacheLayer<T> {
|
||||||
|
private store = new Map<string, { data: T; expires: number; lastAccess: number }>();
|
||||||
|
constructor(private maxSize = 500, private defaultTTL = 5 * 60 * 1000) {}
|
||||||
|
|
||||||
|
set(key: string, value: T, ttl?: number): void {
|
||||||
|
if (this.store.size >= this.maxSize) {
|
||||||
|
let lruKey = "";
|
||||||
|
let lruTime = Infinity;
|
||||||
|
for (const [k, v] of this.store) {
|
||||||
|
if (v.lastAccess < lruTime) { lruTime = v.lastAccess; lruKey = k; }
|
||||||
|
}
|
||||||
|
if (lruKey) this.store.delete(lruKey);
|
||||||
|
}
|
||||||
|
this.store.set(key, { data: value, expires: Date.now() + (ttl ?? this.defaultTTL), lastAccess: Date.now() });
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string): T | null {
|
||||||
|
const e = this.store.get(key);
|
||||||
|
if (!e) return null;
|
||||||
|
if (Date.now() > e.expires) { this.store.delete(key); return null; }
|
||||||
|
e.lastAccess = Date.now();
|
||||||
|
return e.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key: string): void { this.store.delete(key); }
|
||||||
|
clear(): void { this.store.clear(); }
|
||||||
|
size(): number { return this.store.size; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenBucket {
|
||||||
|
private tokens: number;
|
||||||
|
private lastRefill: number;
|
||||||
|
constructor(private capacity: number, private refillRate: number) {
|
||||||
|
this.tokens = capacity;
|
||||||
|
this.lastRefill = Date.now();
|
||||||
|
}
|
||||||
|
tryConsume(n = 1): boolean {
|
||||||
|
const elapsed = (Date.now() - this.lastRefill) / 1000;
|
||||||
|
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
|
||||||
|
this.lastRefill = Date.now();
|
||||||
|
if (this.tokens >= n) { this.tokens -= n; return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
availableTokens(): number {
|
||||||
|
const elapsed = (Date.now() - this.lastRefill) / 1000;
|
||||||
|
return Math.min(this.capacity, Math.floor(this.tokens + elapsed * this.refillRate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionCache = new CacheLayer<ChatSession>(200, 3 * 60 * 1000);
|
||||||
|
const modelCache = new CacheLayer<ModelInfo[]>(10, 30 * 60 * 1000);
|
||||||
|
|
||||||
|
const userBucketStore = new Map<number, BucketEntry>();
|
||||||
|
|
||||||
|
let elevenLabsKeyIndex = 0;
|
||||||
|
let cfAccountIndex = 0;
|
||||||
|
const MODEL_CACHE_TTL = 12 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let _cachedBotConfig: any | null = null;
|
||||||
|
let _configCacheTs = 0;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const TOOLS_CACHE = { user: null as any, admin: null as any };
|
||||||
|
|
||||||
|
const COAXYS_OWNER_ID = 7826271898;
|
||||||
|
|
||||||
|
function setCfg(val: typeof cfg) { cfg = val; }
|
||||||
|
function setApiUrl(val: string) { API_URL = val; }
|
||||||
|
function setBotInfo(val: TgUser | null) { BOT_INFO = val; }
|
||||||
|
function setBotSelfId(val: number | null) { BOT_SELF_ID = val; }
|
||||||
|
function setIsInitialized(val: boolean) { isInitialized = val; }
|
||||||
|
function setMaintenanceCache(val: { value: boolean; ts: number } | null) { maintenanceCache = val; }
|
||||||
|
function setRequestOrigin(val: string) { requestOrigin = val; }
|
||||||
|
function setGlobalCtx(val: ExecutionContext | null) { globalCtx = val; }
|
||||||
|
function setGlobalDisabledKeys(val: Record<string, number>) { globalDisabledKeys = val; }
|
||||||
|
function setSaveDisabledKeysTimer(val: ReturnType<typeof setTimeout> | null) { _saveDisabledKeysTimer = val; }
|
||||||
|
function setLastDisabledKeysFetch(val: number) { lastDisabledKeysFetch = val; }
|
||||||
|
function setCachedBotConfig(val: any | null) { _cachedBotConfig = val; }
|
||||||
|
function setConfigCacheTs(val: number) { _configCacheTs = val; }
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function setToolsCache(key: 'user' | 'admin', val: any) { TOOLS_CACHE[key] = val; }
|
||||||
|
function setElevenLabsKeyIndex(val: number) { elevenLabsKeyIndex = val; }
|
||||||
|
function setCfAccountIndex(val: number) { cfAccountIndex = val; }
|
||||||
|
|
||||||
|
export {
|
||||||
|
cfg,
|
||||||
|
API_URL,
|
||||||
|
BOT_INFO,
|
||||||
|
BOT_SELF_ID,
|
||||||
|
isInitialized,
|
||||||
|
maintenanceCache,
|
||||||
|
requestOrigin,
|
||||||
|
globalCtx,
|
||||||
|
runtimeStatus,
|
||||||
|
activeRequests,
|
||||||
|
callbackRateLimits,
|
||||||
|
sessionLoadLocks,
|
||||||
|
groupContextCache,
|
||||||
|
modelListStates,
|
||||||
|
adminPanelStates,
|
||||||
|
broadcastStates,
|
||||||
|
globalDisabledKeys,
|
||||||
|
_saveDisabledKeysTimer,
|
||||||
|
lastDisabledKeysFetch,
|
||||||
|
activeProgressMessages,
|
||||||
|
lastTypingSent,
|
||||||
|
MAX_LOGS,
|
||||||
|
recentLogs,
|
||||||
|
log,
|
||||||
|
logger,
|
||||||
|
CacheLayer,
|
||||||
|
TokenBucket,
|
||||||
|
sessionCache,
|
||||||
|
modelCache,
|
||||||
|
userBucketStore,
|
||||||
|
elevenLabsKeyIndex,
|
||||||
|
cfAccountIndex,
|
||||||
|
MODEL_CACHE_TTL,
|
||||||
|
_cachedBotConfig,
|
||||||
|
_configCacheTs,
|
||||||
|
TOOLS_CACHE,
|
||||||
|
COAXYS_OWNER_ID,
|
||||||
|
setCfg,
|
||||||
|
setApiUrl,
|
||||||
|
setBotInfo,
|
||||||
|
setBotSelfId,
|
||||||
|
setIsInitialized,
|
||||||
|
setMaintenanceCache,
|
||||||
|
setRequestOrigin,
|
||||||
|
setGlobalCtx,
|
||||||
|
setGlobalDisabledKeys,
|
||||||
|
setSaveDisabledKeysTimer,
|
||||||
|
setLastDisabledKeysFetch,
|
||||||
|
setCachedBotConfig,
|
||||||
|
setConfigCacheTs,
|
||||||
|
setToolsCache,
|
||||||
|
setElevenLabsKeyIndex,
|
||||||
|
setCfAccountIndex,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user