"use strict";function e(e){const t=e.headers["x-real-ip"];if(t)return t;const s=e.headers["x-forwarded-for"];if(s){return s.split(",")[0].trim()}const i=e.socket?.remoteAddress||e.connection?.remoteAddress;return i||""}class t{constructor({tokenPerSecond:e,capacity:t,keyPrefix:s=""}={}){this.tokenPerSecond=e,this.capacity=t,this.keyPrefix=s,this.tokens={},this.lastTime={},this.cleanupInterval=36e5,this.expirationThreshold=864e5,setInterval((()=>{const e=Date.now();Object.keys(this.tokens).forEach((t=>{e-this.lastTime[t]>this.expirationThreshold&&(delete this.tokens[t],delete this.lastTime[t])}))}),this.cleanupInterval)}getTokenUseIp(t,s=""){const i=e(t);return this.getToken(i+s)}getToken(e="RateLimiterTokenBucketGlobalKey"){const t=this.keyPrefix+e,s=Date.now();this.tokens[t]||(this.tokens[t]=this.capacity,this.lastTime[t]=s);const i=Math.max(0,s-this.lastTime[t])*this.tokenPerSecond/1e3;this.tokens[t]=Math.min(this.tokens[t]+i,this.capacity),this.lastTime[t]=s;const n=this.tokens[t];return n>0&&this.tokens[t]--,n}}exports.RateLimiterTokenBucket=t,exports.RateLimiterTokenBucketRedis=class{constructor(e){this.tokenPerSecond=e.tokenPerSecond,this.capacity=e.capacity,this.redis=e.redisClient,this.keyPrefix=e.keyPrefix,this.insuranceLimiter=e.insuranceLimiter,this.inMemoryBlockOnConsumed=e.inMemoryBlockOnConsumed,this.inMemoryBlockDuration=e.inMemoryBlockDuration,this.blockedKeys=new Map,this.redis||import("ioredis").then((t=>{this.redis=new t(e.redisOptions)})).catch((()=>{console.warn("ioredis module not found. Please provide a redisClient when creating an instance of RateLimiterTokenBucketRedis.")})),this.insuranceLimiter&&(this.rateLimiterTokenBucket=new t({tokenPerSecond:e.insuranceLimiterTokenPerSecond||this.tokenPerSecond,capacity:e.insuranceLimiterCapacity||this.capacity,keyPrefix:this.keyPrefix})),this._initScript()}_initScript(){this.script=`\n -- 获取键名、容量、当前时间\n local key = KEYS[1]\n local capacity = tonumber(ARGV[1])\n local now = tonumber(ARGV[2])\n\n -- 获取上次请求时间和当前令牌数,如果不存在则分别设置为当前时间和容量\n local lastTime = tonumber(redis.call('HGET', key, 'lastTime')) or now\n local tokens = tonumber(redis.call('HGET', key, 'tokens')) or capacity\n\n -- 计算时间差和应生成的令牌数\n local deltaMS = math.max(0, now - lastTime)\n local deltaTokens = math.min(deltaMS * ${this.tokenPerSecond} / 1000, capacity - tokens)\n\n -- 更新令牌数,不能超过容量\n tokens = math.min(tokens + deltaTokens, capacity)\n\n -- 更新上次请求时间和当前令牌数\n redis.call('HSET', key, 'lastTime', now)\n redis.call('HSET', key, 'tokens', tostring(tokens)) -- 将tokens转换为字符串\n\n -- 设置键的过期时间为60秒\n redis.call('EXPIRE', key, 60)\n\n -- 如果令牌数小于1,则返回0,否则减少一个令牌并返回当前令牌数\n if tokens < 1 then\n return 0\n else\n redis.call('HSET', key, 'tokens', tostring(tokens - 1)) -- 实现浮点数自减\n return tokens -- 返回当前令牌数\n end\n `}async getTokenUseIp(t,s="",i=""){const n=e(t);return await this.getToken(n+s,i||n)}async getToken(e,t){const s=this.keyPrefix+e,i=t?this.keyPrefix+t:s;if(this._isKeyBlocked(i))return 0;if(this.inMemoryBlockOnConsumed){const{consumedTokens:e,lastRequest:t}=this.blockedKeys.get(`${i}:consumed`)||{consumedTokens:1,lastRequest:Date.now()};Date.now()-t>=6e4?(this.blockedKeys.delete(`${i}:consumed`),this.blockedKeys.set(`${i}:consumed`,{consumedTokens:e+1,lastRequest:Date.now()})):e>=this.inMemoryBlockOnConsumed?this._blockKey(i):(this.blockedKeys.set(`${i}:consumed`,{consumedTokens:e+1,lastRequest:Date.now()}),this.blockedKeys.size>0&&this._collectExpiredBlockedKeys())}try{if(this._isRedisReady()){return await this.redis.eval(this.script,1,s,this.capacity,Date.now())}return this.insuranceLimiter?await this.rateLimiterTokenBucket.getToken(s):1}catch(e){return this.insuranceLimiter?await this.rateLimiterTokenBucket.getToken(s):1}}_blockKey(e){this.blockedKeys.set(e,Date.now()+1e3*this.inMemoryBlockDuration)}_collectExpiredBlockedKeys(){const e=Date.now();for(const[t,s]of this.blockedKeys.entries())"number"==typeof s&&s<=e&&this.blockedKeys.delete(t),t.includes("consumed")&&e-s.lastRequest>=6e4&&this.blockedKeys.delete(t)}_isRedisReady(){return!this.redis.status||"ready"===this.redis.status}_isKeyBlocked(e){if(!this.blockedKeys.has(e))return!1;const t=this.blockedKeys.get(e);return Date.now()