const EXT_VERSION="139",tabCooldown=new Map,isCooling=e=>(tabCooldown.get(e)||0)>Date.now(),penalize=(e,t=15e3)=>tabCooldown.set(e,Date.now()+t);let lastPayload="",lastHash="",lastHeaders={},ahWs=null,lastStatsDaily=null,wsSid=null,wsAuth=null,wsUrl=null,wsPingTmr=null,wsReconnectTmr=null,wsReconnectAttempts=0,wsActive=!1,wsConnecting=!1,wsManualClose=!1,bearerToken=null,lastSenderTabId=null,operatorEmail="";
function loadOperatorEmail(){try{chrome.storage.local.get(["ahOperatorEmail"],(e=>{operatorEmail=String(e&&e.ahOperatorEmail||"").trim()}))}catch{}}loadOperatorEmail();try{chrome.storage.onChanged.addListener(((e,t)=>{"local"===t&&e.ahOperatorEmail&&(operatorEmail=String(e.ahOperatorEmail.newValue||"").trim())}))}catch{}chrome.runtime.onInstalled.addListener((async e=>{try{const t=await new Promise((e=>chrome.storage.local.get(["ahRunning","ahLastPayload","ahLastHash","ahLastHeaders","ahSet","ahOperatorEmail"],e)));operatorEmail=String(t&&t.ahOperatorEmail||"").trim();if(chrome.storage.local.set({ahLastVersion:EXT_VERSION}),"install"===e.reason){await chrome.storage.local.set({ahRunning:!1,ahKeepAwake:!1}),stopOnlineStatusLoop(),releasePulseKeepAwake(),setBadge("Stopped");try{chrome.alarms.clear("ahUpdate")}catch{}try{ahWs&&ahWs.close()}catch{}return ahWs=null,wsActive=!1,chrome.storage.local.get(["ahAutoBackup","ahLastBackup"],(e=>{void 0===e.ahAutoBackup?chrome.storage.local.set({ahAutoBackup:!0,ahLastBackup:Date.now()}):e.ahAutoBackup&&void 0===e.ahLastBackup&&chrome.storage.local.set({ahLastBackup:Date.now()})})),void(t.ahSet&&t.ahSet.running&&(t.ahSet.running=!1,chrome.storage.local.set({ahSet:t.ahSet})))}"update"===e.reason&&t.ahRunning&&(await doBotAction({run:"Stop",json:t.ahLastPayload||"",hash:t.ahLastHash||"",key:(t.ahLastHeaders||{})["AH-Auth"]||"",opId:(t.ahLastHeaders||{})["AH-OperatorID"]||""},{}),t.ahSet&&t.ahSet.running&&(t.ahSet.running=!1,chrome.storage.local.set({ahSet:t.ahSet})))}catch(e){console.warn("[onInstalled] force-stop failed",e),stopOnlineStatusLoop(),wsActive=!1;try{ahWs&&ahWs.close()}catch{}ahWs=null,releasePulseKeepAwake(),setBadge("Stopped"),chrome.storage.local.set({ahRunning:!1,ahKeepAwake:!1});try{chrome.alarms.clear("ahUpdate")}catch{}}})),chrome.storage.local.get("ahJwt",(e=>{bearerToken=e.ahJwt||null}));const FULL_TABLE_CMD="BuildFullTable",FT_RESUME_KEY="ahFTResume";let ftResumePage=0;chrome.storage.local.get(FT_RESUME_KEY,(e=>{ftResumePage=Number(e[FT_RESUME_KEY])||0}));const PULSAR_AUTH_KEY="pulsarAuth";function readLocal(keys){return new Promise((resolve=>chrome.storage.local.get(keys,resolve)))}async function requirePulsarAuth(expectedToken=null){const data=await readLocal([PULSAR_AUTH_KEY]);const auth=data&&data[PULSAR_AUTH_KEY]||null;const ok=!!(auth&&auth.authenticated&&typeof auth.token==="string"&&auth.token.trim());if(!ok)return{ok:!1,error:"Please login to use this feature"};if(expectedToken&&String(expectedToken).trim()!==String(auth.token).trim())return{ok:!1,error:"Please login again"};return{ok:!0,auth}}
function requestPulseKeepAwake(){
  try{
    if(chrome.power && chrome.power.requestKeepAwake){
      chrome.power.requestKeepAwake("display");
      chrome.storage.local.set({ahKeepAwake:!0});
    }
  }catch(e){
    console.warn("[keepAwake] request failed",e);
  }
}
function releasePulseKeepAwake(){
  try{
    if(chrome.power && chrome.power.releaseKeepAwake){
      chrome.power.releaseKeepAwake();
      chrome.storage.local.set({ahKeepAwake:!1});
    }
  }catch(e){
    console.warn("[keepAwake] release failed",e);
  }
}
const DEFAULT_ENDPOINT="https://pulsarcloud.net/";let ahEndpoint=DEFAULT_ENDPOINT;function stopOnlineStatusLoop(){}function startOnlineStatusLoop(){}function clearWsPingTimer(){wsPingTmr&&(clearInterval(wsPingTmr),wsPingTmr=null)}function clearWsReconnectTimer(){wsReconnectTmr&&(clearTimeout(wsReconnectTmr),wsReconnectTmr=null)}function scheduleWsReconnect(e="unknown"){if(!wsActive||wsManualClose||!wsSid||!wsAuth)return;if(wsReconnectTmr||wsConnecting||1===ahWs?.readyState||0===ahWs?.readyState)return;const t=Math.min(3e4,Math.max(1500,1500*Math.min(++wsReconnectAttempts,5)));console.log(`[WS] reconnect scheduled in ${t}ms reason=${e} attempt=${wsReconnectAttempts}`),wsReconnectTmr=setTimeout((()=>{wsReconnectTmr=null,wsActive&&!wsManualClose&&connectWs(wsSid,wsAuth,wsUrl)}),t)}function switchEndpoint(e){if("string"==typeof e){e=e.endsWith("/")?e:e+"/";if(e!==ahEndpoint){ahEndpoint=e,chrome.storage.local.set({ahBaseHost:e}),wsManualClose=!0,clearWsReconnectTimer(),clearWsPingTimer();if(ahWs){try{ahWs.close()}catch(e){}ahWs=null}wsConnecting=!1,wsUrl=null,wsSid=null,wsAuth=null}}}const profilesCache={list:null,ts:0};async function getProfiles(e,t=5e3){if(!e)return[];const a=Date.now();if(profilesCache.list&&a-profilesCache.ts<t)return profilesCache.list;try{const t=await alphaFetchViaAnyTab("/api/operator/profiles",{method:"GET",headers:{authorization:"Bearer "+e}}),r=(new TextDecoder).decode(b64ToUint8(t.bodyBase64||"")),s=JSON.parse(r);if(Array.isArray(s))return profilesCache.list=s,profilesCache.ts=a,s}catch{}return[]}async function fetchCurrentOperatorId(e,t=null){if(!e)return null;const a={authorization:"Bearer "+e,"content-type":"application/json"},r=Array.isArray(t)?t:await getProfiles(e),s=r.length?r[0]:null;if(!s)return null;const{external_id:o,id:n}=s;try{const e=await alphaFetchViaAnyTab(`/api/sender/inviteList?external_id=${o}&mail_type=Chat`,{method:"GET",headers:a}),t=(new TextDecoder).decode(b64ToUint8(e.bodyBase64||"")),r=Array.isArray(JSON.parse(t))?JSON.parse(t):[];try{const e=r.filter((e=>"string"==typeof e?.message_content&&"test"===e.message_content.trim().toLowerCase()));e.length&&await Promise.allSettled(e.map((e=>alphaFetchViaAnyTab(`/api/sender/deleteInvite?external_id=${o}&id=${e.id}&mail_type=Chat`,{method:"GET",headers:a}))))}catch{}const s=r.find((e=>e.operator_id));if(s)return s.operator_id}catch{}try{const e=JSON.stringify({external_id:o,woman_id:n,message_content:"test",message_type:"SENT_TEXT",sender_type:"chat"}),t=await alphaFetchViaAnyTab("/api/sender/addInvite",{method:"POST",headers:a,bodyStr:e}),r=(new TextDecoder).decode(b64ToUint8(t.bodyBase64||"")),s=JSON.parse(r)||{},i=Array.isArray(s?.inviteList)?s.inviteList:[],c=i.find((e=>e.operator_id)),l=c?.operator_id||null,h=i.find((e=>"string"==typeof e?.message_content&&"test"===e.message_content.trim().toLowerCase()));if(h?.id)try{await alphaFetchViaAnyTab(`/api/sender/deleteInvite?external_id=${o}&id=${h.id}&mail_type=Chat`,{method:"GET",headers:a})}catch{}return l}catch{return null}}function waitTabComplete(e,t=3500){return new Promise((a=>{try{chrome.tabs.get(e,(e=>chrome.runtime.lastError||!e?a(!1):"complete"===e.status?a(!0):void 0))}catch{return a(!1)}const r=(t,s)=>{t===e&&"complete"===s.status&&(chrome.tabs.onUpdated.removeListener(r),a(!0))};chrome.tabs.onUpdated.addListener(r),setTimeout((()=>{try{chrome.tabs.onUpdated.removeListener(r)}catch{}a(!1)}),t)}))}async function waitContentReady(e,t=6e3){const a=Date.now();for(;Date.now()-a<t;){try{const t=await sendToTabWithTimeout(e,{cmd:"ah_ping"},800);if(t&&t.ok&&t.bridge)return!0}catch{}await waitTabComplete(e,1200)}return!1}async function getAlphaTabs(){return new Promise((e=>chrome.tabs.query({url:"https://alpha.date/*"},(t=>e((t||[]).filter((e=>!e.discarded)))))))}function orderAlphaTabs(e){const t=[...e||[]];if(lastSenderTabId){const e=t.findIndex((e=>e.id===lastSenderTabId));if(e>=0){const[a]=t.splice(e,1);t.unshift(a)}}return t}function sendToTabWithTimeout(e,t,a=8e3){return new Promise(((r,s)=>{let o=!1;const n=setTimeout((()=>{o||(o=!0,s(new Error("timeout")))}),a);try{chrome.tabs.sendMessage(e,t,(e=>{o||(clearTimeout(n),chrome.runtime.lastError||!e?s(new Error(chrome.runtime.lastError?.message||"no receiver")):r(e))}))}catch(e){clearTimeout(n),s(e)}}))}function killWsNoTabs(){try{wsActive=!1,wsManualClose=!0}catch{}clearWsReconnectTimer(),clearWsPingTimer();if(ahWs){try{ahWs.close()}catch{}ahWs=null}wsConnecting=!1}const HEAVY_READ_LIMIT=2;let heavyReadActive=0;const heavyReadQueue=[];function isHeavyReadPath(e,t="GET"){const a=String(t||"GET").toUpperCase();if("GET"!==a&&"POST"!==a)return!1;return/^\/api\/chatList\/(chatHistory|lastMessage|userDetail|chatListByUserID)\b/i.test(String(e||""))}function acquireHeavyReadSlot(e=15e3){return new Promise(((t,a)=>{if(heavyReadActive<HEAVY_READ_LIMIT)return heavyReadActive++,void t((()=>releaseHeavyReadSlot()));const r=()=>{o&&clearTimeout(o),heavyReadActive++,t((()=>releaseHeavyReadSlot()))},o=e>0?setTimeout((()=>{const e=heavyReadQueue.indexOf(r);e>=0&&heavyReadQueue.splice(e,1),a(new Error("heavy read queue timeout"))}),e):null;heavyReadQueue.push(r)}))}function releaseHeavyReadSlot(){heavyReadActive=Math.max(0,heavyReadActive-1);const e=heavyReadQueue.shift();if(e)try{e()}catch{}}async function alphaFetchViaAnyTab(e,{method:t="GET",headers:a={},bodyStr:r=null,referer:s=null}={},o=5e3,n={}){const i=isHeavyReadPath(e,t)?await acquireHeavyReadSlot(Math.max(4e3,Math.min(15e3,2*o))):null;try{const i=await getAlphaTabs();if(!i.length)throw new Error("no alpha tabs");const c=orderAlphaTabs(i.filter((e=>!isCooling(e.id)))),l=c.length?c:orderAlphaTabs(i);let h;for(const i of l){const c=Date.now(),l=await waitContentReady(i.id,Math.min(Math.floor(.6*o),4e3)),d=o-(Date.now()-c);if(!l||d<=300)penalize(i.id,n.cooldownMs||1e4);else try{const o=await sendToTabWithTimeout(i.id,{cmd:"fetchViaPage",path:e,method:t,headers:a,bodyBase64:r?uint8ToB64((new TextEncoder).encode(r)):null,timeout:Math.max(500,d-200),bridgeTimeout:n.bridgeTimeoutMs||1200,referer:s||null},Math.max(800,d));if(o?.ok)return lastSenderTabId=i.id,o;penalize(i.id,n.cooldownMs||1e4),h=new Error(o?.error||"bad response")}catch(e){penalize(i.id,n.cooldownMs||1e4),h=e}}throw h||new Error("all tabs failed")}finally{try{i&&i()}catch{}}}async function ensureEndpointLoaded(){if(ahEndpoint===DEFAULT_ENDPOINT)try{const e=await new Promise((e=>chrome.storage.local.get("ahBaseHost",e)));if("string"==typeof e.ahBaseHost&&e.ahBaseHost){const t=e.ahBaseHost.trim();if(/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/?$/i.test(t))ahEndpoint=DEFAULT_ENDPOINT,chrome.storage.local.set({ahBaseHost:DEFAULT_ENDPOINT});else ahEndpoint=t.endsWith("/")?t:t+"/"}}catch{}}function makeWsUrl(e,t){return"string"==typeof e&&0===e.indexOf("http://")?e.replace(/^http:/,"ws:").replace(/\/$/,"")+"/client/"+t:e.replace(/^https:/,"wss:").replace(/\/$/,"")+"/client/"+t}function uint8ToB64(e){let t="";for(let a=0;a<e.length;a+=32768)t+=String.fromCharCode(...e.subarray(a,a+32768));return btoa(t)}async function getBearerToken(){return bearerToken||new Promise((e=>{chrome.storage.local.get("ahJwt",(t=>{bearerToken=t.ahJwt||null,e(bearerToken)}))}))}function askForFreshPayload(e=1e4){return new Promise((t=>{let a=!1;chrome.tabs.query({},(e=>{for(const r of e)chrome.tabs.sendMessage(r.id,{cmd:"getPayload"},(e=>{a||!chrome.runtime.lastError&&e?.ok&&(a=!0,t(e))}))})),setTimeout((()=>{a||t(null)}),e)}))}function dispatchServerLogToTabs(e){try{chrome.tabs.query({url:"https://alpha.date/*"},(t=>{for(const a of t||[])try{a&&a.id&&chrome.tabs.sendMessage(a.id,{type:"PULSAR_SERVER_LOG",payload:e})}catch{}}))}catch{}}function handleWsInbound(e){if(saveBalanceFromAny(e),"server_log"===e?.type)return void dispatchServerLogToTabs(e);e.action!==FULL_TABLE_CMD?e.method&&e.path&&proxyFetch(e):(async()=>{try{await buildFullTable(e)}catch(e){killWsNoTabs()}})()}function connectWs(e,t,a){if(!wsActive)return Promise.resolve();if(wsConnecting||1===ahWs?.readyState||0===ahWs?.readyState)return Promise.resolve();return new Promise((r=>{wsSid=e,wsAuth=t,wsUrl=a||wsUrl||makeWsUrl(ahEndpoint,e),wsManualClose=!1,clearWsReconnectTimer(),clearWsPingTimer(),wsConnecting=!0;const s=new WebSocket(`${wsUrl}?AH-Auth=${t}`);ahWs=s;const o=setTimeout((()=>{if(ahWs===s&&1!==s.readyState){console.warn("[WS] open timeout, force close");try{s.close()}catch{}}r()}),8e3);s.onopen=()=>{if(ahWs!==s)return void r();clearTimeout(o),wsConnecting=!1,wsReconnectAttempts=0,console.log("[WS] open",e),clearWsPingTimer(),wsPingTmr=setInterval((()=>{if(ahWs!==s)return;try{1===s.readyState&&s.send('{"type":"ping"}')}catch(e){console.warn("[WS] ping err",e)}}),25e3),lastSenderTabId||chrome.tabs.query({url:"https://alpha.date/*"},(e=>{e.length&&(lastSenderTabId=e[0].id,ensureWsPort(lastSenderTabId))})),r()},s.onerror=e=>{ahWs===s&&console.warn("[WS] err",e)},s.onclose=e=>{if(ahWs!==s)return void r();clearTimeout(o),console.log("[WS] close",e?.code,e?.reason||""),clearWsPingTimer(),ahWs=null,wsConnecting=!1,scheduleWsReconnect(`close:${e?.code||0}`),r()};let n="";s.onmessage=async e=>{if(ahWs!==s)return;let t=e.data;t instanceof Blob&&(t=await t.text()),t=t.replace(/\r?\n$/,""),n+=t;try{const e=JSON.parse(n);n="",handleWsInbound(e)}catch{}}}))}function b64ToUint8(e){const t=atob(e);return Uint8Array.from(t,(e=>e.charCodeAt(0)))}function b64ToUtf8(e){return new TextDecoder("utf-8").decode(b64ToUint8(e))}async function proxyFetch(e){const t=async()=>{const t={...e.headers};if(!("Authorization"in t)){const e=await getBearerToken();e&&(t.Authorization="Bearer "+e)}const a=e.body?b64ToUtf8(e.body):null,r=alphaFetchViaAnyTab(e.path,{method:e.method,headers:t,bodyStr:a,referer:e.referer||null},Math.max(14500,500));return await Promise.race([r,new Promise(((e,t)=>setTimeout((()=>t(new Error("timeout"))),15e3)))])},a=e=>{try{const t=e?.headers||{},a=String(t["content-type"]||t["Content-Type"]||"").toLowerCase(),r=!(!t["cf-ray"]&&!t["CF-RAY"])||String(t.server||t.Server||"").toLowerCase().includes("cloudflare");if(!a.includes("text/html")&&!r)return!1;const s=(new TextDecoder).decode(b64ToUint8(e.bodyBase64||"")).slice(0,4096),o=/Just a moment/i.test(s)||/cf[-_]?challenge/i.test(s)||/cdn-cgi\/challenge-platform/i.test(s)||/name="cf_chl_/i.test(s)||/turnstile/i.test(s)||/hcaptcha/i.test(s);return a.includes("text/html")&&o||r&&(403===e.status||503===e.status||a.includes("text/html"))}catch{return!1}};let r;try{r=await t()}catch(e){try{if(!(await getAlphaTabs()).length)return void killWsNoTabs()}catch{return void killWsNoTabs()}try{r=await t()}catch(e){return void killWsNoTabs()}}if(a(r)){await sleep(1e4);try{r=await t()}catch{return void killWsNoTabs()}if(a(r))return void killWsNoTabs()}let s=r.bodyBase64;if(("string"==typeof e.path?e.path:"").toLowerCase().includes("personal"))try{const e=JSON.parse((new TextDecoder).decode(b64ToUint8(s))),t=Math.floor(Date.now()/1e3),a=900,r=(e,r=!1)=>Array.isArray(e)?[...new Map(e.filter((e=>{const r=Number(e?.userProfileAlias?.last_online)||0;return!!r&&t-r<=a})).map((e=>[e?.man_external_id,{man_external_id:e?.man_external_id,active:r?0:Number(e?.active)||0,created_at:e?.created_at}]))).values()]:[];if(e&&(Array.isArray(e.newList)||Array.isArray(e.historyList))){const t={newList:r(e.newList,!0),historyList:r(e.historyList,!1)};s=uint8ToB64((new TextEncoder).encode(JSON.stringify(t)))}}catch{}try{ahWs?.send(JSON.stringify({id:e.id,status:r.status,headers:r.headers,body:s}))}catch{}}async function buildFullTable({id:e,firstPageOnly:t=0}){const a=await getBearerToken(),r=a?{authorization:"Bearer "+a}:{},s=performance.now(),o=ftResumePage||0,n=[],i=new Set;async function c(e,t=!1){const a=[e];let o=!1;for(let e=0;e<a.length;e+=1){const t=a.slice(e,e+1);for(const e of t)if(performance.now()-s>90000)return ftResumePage=e,chrome.storage.local.set({[FT_RESUME_KEY]:ftResumePage}),{aborted:!0,end:!1};const c=await Promise.all(t.map((async e=>{try{return{ok:!0,p:e,json:await fetchJSON("/api/chatList/chatListByUserID",{user_id:"",chat_uid:!1,page:e,freeze:!0,limits:null,ONLINE_STATUS:1,SEARCH:"",CHAT_TYPE:"CHANCE"},r,{referer:"https://alpha.date/chance",perTryMs:15000,attempts:4})}}catch(t){return{ok:!1,p:e,err:t}}}))),l=c.filter((e=>e.ok)).sort(((e,t)=>e.p-t.p)),h=c.filter((e=>!e.ok)).sort(((e,t)=>e.p-t.p));if(!l.length)return ftResumePage=h[0].p,chrome.storage.local.set({[FT_RESUME_KEY]:ftResumePage}),{aborted:!0,end:!1};const d=l.map((({p:e,json:t})=>{const a=Array.isArray(t?.response)?t.response:[],r=a.filter((e=>e&&0===e.female_block));return a.length<30&&(o=!0),{p:e,rows:r}})),u=Array.from(new Set(d.flatMap((e=>e.rows.map((e=>e.chat_uid))))));let m={response:[]},p={response:[]};if(u.length)try{[m,p]=await Promise.all([fetchJSON("/api/chatList/userDetail",{chat_uid:u},r,{referer:"https://alpha.date/chance",perTryMs:15000,attempts:4}),fetchJSON("/api/chatList/lastMessage",{chat_uid:u},r,{referer:"https://alpha.date/chance",perTryMs:15000,attempts:4})])}catch{return ftResumePage=l[0].p,chrome.storage.local.set({[FT_RESUME_KEY]:ftResumePage}),{aborted:!0,end:!1}}const f=Object.fromEntries((m?.response||[]).map((e=>[e.chat_uid,e]))),y=Object.fromEntries((p?.response||[]).map((e=>[e.chat_uid,e])));for(const{p:e,rows:t}of d){if(!t.every((e=>f[e.chat_uid]&&y[e.chat_uid])))return ftResumePage=e,chrome.storage.local.set({[FT_RESUME_KEY]:ftResumePage}),{aborted:!0,end:!1};for(const e of t){const t=e.chat_uid;i.has(t)||(i.add(t),n.push({...e,...f[t]||{},...y[t]||{}}))}}if(h.length)return ftResumePage=h[0].p,chrome.storage.local.set({[FT_RESUME_KEY]:ftResumePage}),{aborted:!0,end:!1}}return{end:o}}if(t)return await c(1,!0),h();{const e=await c(1);if(e?.aborted)return h();if(e?.end)return ftResumePage=0,chrome.storage.local.remove(FT_RESUME_KEY),h()}let l=o?Math.max(4,o-4):4;for(;;){if(performance.now()-s>9e4){ftResumePage=l,chrome.storage.local.set({[FT_RESUME_KEY]:ftResumePage});break}const{end:e,aborted:t}=await c(l);if(t)break;if(e){ftResumePage=0,chrome.storage.local.remove(FT_RESUME_KEY);break}l+=1}return h();function h(){const t=JSON.stringify(n),a=uint8ToB64((new TextEncoder).encode(t));1===ahWs?.readyState&&ahWs.send(JSON.stringify({id:e,status:200,body:a}))}}function sleep(e){return new Promise((t=>setTimeout(t,e)))}function parseRetryAfter(e){if(!e)return 0;const t=Number(e);if(Number.isFinite(t))return Math.max(0,Math.floor(1e3*t));const a=Date.parse(e);return Number.isFinite(a)?Math.max(0,a-Date.now()):0}async function fetchJSON(e,t,a={},r={}){return await pulseRunDedupeJson(e,'POST',a,t,async()=>{const s=Number.isFinite(r.attempts)?Math.max(1,r.attempts):3,o=Number.isFinite(r.perTryMs)?Math.max(800,r.perTryMs):15e3,n={"Content-Type":"application/json",...a},i=JSON.stringify(t||{});let c;for(let t=0;t<s;t++){try{const t=await Promise.race([alphaFetchViaAnyTab(e,{method:"POST",headers:n,bodyStr:i,referer:r.referer||null},Math.max(500,o-200)),new Promise(((e,t)=>setTimeout((()=>t(new Error("timeout"))),o)))]),a=(new TextDecoder).decode(b64ToUint8(t.bodyBase64||"")),s=0|t.status;try{const e=t.headers||{},r=String(e["content-type"]||e["Content-Type"]||"").toLowerCase(),o=!(!e["cf-ray"]&&!e["CF-RAY"])||String(e.server||e.Server||"").toLowerCase().includes("cloudflare"),n=/Just a moment/i.test(a)||/cf[-_]?challenge/i.test(a)||/cdn-cgi\/challenge-platform/i.test(a)||/name="cf_chl_/i.test(a)||/turnstile/i.test(a)||/hcaptcha/i.test(a);if(r.includes("text/html")&&n||o&&(403===s||503===s||r.includes("text/html"))){await sleep(1e4),c=new Error("cloudflare_challenge");continue}}catch{}if(s<200||s>299){const r=new Error(`fetch ${e} → ${s}\n${a}`);if(429===s){const e=parseRetryAfter((t.headers||{})["retry-after"]||(t.headers||{})["Retry-After"]),a=Math.floor(200*Math.random());await sleep(e+a||500+a),c=r;continue}if(s>=400&&s<500&&![408,429].includes(s))throw r;c=r}else try{return JSON.parse(a)}catch(e){c=new Error(`json parse failed: ${e.message}`)}}catch(e){c=e}if(t<s-1){const e=150*Math.pow(2,t),a=Math.floor(120*Math.random());await sleep(e+a)}}throw c||new Error("request failed")})}function ahUrl(e,{auth:t,opId:a,opEmail:n,action:r,hash:s}){const o=(t||"").slice(0,16);return`${e}?${new URLSearchParams({"AH-Auth":o,"AH-OperatorID":a||"","AH-OperatorEmail":n||"","AH-Action":r||"","AH-JSON-SHA1":s||"","AH-JSON-Len":String(lastPayload?.length||0),ver:EXT_VERSION}).toString()}`}function handleForbidden(e){const t=e&&403===e.status&&"ok"===e.headers.get("AH-Origin");return t&&(chrome.storage.local.remove(["ahLastPayload","ahLastHash","ahLastHeaders","ahRunning"]),lastPayload="",lastHash="",lastHeaders={},stopOnlineStatusLoop(),setBadge("None"),chrome.tabs.query({},(e=>{for(const t of e)chrome.tabs.sendMessage(t.id,{cmd:"resetAuth"},(()=>{chrome.runtime.lastError}))}))),t}function purgeAuth(){chrome.storage.local.remove(["ahLastPayload","ahLastHash","ahLastHeaders","ahRunning"]),lastPayload="",lastHash="",lastHeaders={},stopOnlineStatusLoop(),wsActive=!1,wsManualClose=!0,wsConnecting=!1,clearWsReconnectTimer(),clearWsPingTimer(),ahWs&&(ahWs.close(),ahWs=null),setBadge("None"),chrome.tabs.query({},(e=>{for(const t of e)chrome.tabs.sendMessage(t.id,{cmd:"resetAuth"},(()=>{chrome.runtime.lastError}))}))}function setBadge(e){"Running"===e?(chrome.action.setBadgeText({text:"▶"}),chrome.action.setBadgeBackgroundColor({color:"#4caf50"})):"Stopped"===e?(chrome.action.setBadgeText({text:"Ⅱ"}),chrome.action.setBadgeBackgroundColor({color:"#d32f2f"})):(chrome.action.setBadgeText({text:""}),chrome.action.setBadgeBackgroundColor({color:[0,0,0,0]}))}
function saveBalanceFromAny(obj){
  try{
    if(!obj||typeof obj!=="object") return;
    const b=(obj.balance&&typeof obj.balance==="object")?obj.balance:obj;
    const today=Number(b.today);
    const total=Number(b.total);
    if(!Number.isFinite(today)&&!Number.isFinite(total)) return;
    const rec={ts:Date.now()};
    if(Number.isFinite(today)) rec.today=today;
    if(Number.isFinite(total)) rec.total=total;
    chrome.storage.local.set({ahBalance:rec});
  }catch{}
}
async function extractExpSec(e){try{const t=await e.clone().json();if(t.endpoint&&switchEndpoint(t.endpoint),"number"==typeof t.exp_sec){const e=t.exp_sec;chrome.tabs.query({},(t=>{for(const a of t)chrome.tabs.sendMessage(a.id,{cmd:"saveExp",exp_sec:e},(()=>{chrome.runtime.lastError}))}))}Array.isArray(t.stats_daily)&&t.stats_daily.length&&(lastStatsDaily=t.stats_daily[0]&&"object"==typeof t.stats_daily[0]?t.stats_daily[0]:lastStatsDaily);if(t.balance&&"object"==typeof t.balance){saveBalanceFromAny(t.balance)}}catch{}}async function sendUpdate(){if(checkAutoBackup(),ahWs&&0===ahWs.readyState){console.warn("[WS] stuck in CONNECTING, reset");try{ahWs.close()}catch{}ahWs=null}const e=await askForFreshPayload();if(!lastHeaders?.["AH-Auth"])return;e?.ok&&(lastPayload=e.json,lastHash=e.hash,chrome.storage.local.set({ahLastPayload:lastPayload,ahLastHash:lastHash}));const t=await getBearerToken(),a=await getProfiles(t);if(!a.some((e=>1===Number(e.online??e.is_online))))return wsActive=!1,wsPingTmr&&(clearInterval(wsPingTmr),wsPingTmr=null),void(ahWs&&(ahWs.close(),ahWs=null));const r=await fetchCurrentOperatorId(t,a);if(r&&r!==lastHeaders["AH-OperatorID"])return wsActive=!1,wsPingTmr&&(clearInterval(wsPingTmr),wsPingTmr=null),void(ahWs&&(ahWs.close(),ahWs=null));await ensureEndpointLoaded();const s=ahUrl(ahEndpoint,{auth:lastHeaders["AH-Auth"],opId:lastHeaders["AH-OperatorID"],opEmail:operatorEmail,action:"Start",hash:lastHash});try{const e=await fetch(s,{method:"POST",body:lastPayload,keepalive:!0});handleForbidden(e),extractExpSec(e);let t={};try{t=await e.clone().json()}catch{}t.sid&&t.wss_url&&t.auth&&!ahWs&&(wsActive=!0,await connectWs(t.sid,t.auth,t.wss_url),lastSenderTabId&&ensureWsPort(lastSenderTabId))}catch(e){console.warn("[sendUpdate]",e)}}async function checkAutoBackup(){const e=await new Promise((e=>chrome.storage.local.get(["ahAutoBackup","ahLastBackup"],e)));if(!e.ahAutoBackup)return;const t=Date.now();if(!(t-(e.ahLastBackup||0)<864e5))try{await makeBackup(),chrome.storage.local.set({ahLastBackup:t}),console.log("[AB] backup done",new Date(t).toLocaleString())}catch(e){console.warn("[AB] failed:",e)}}async function makeBackup(){const e=await askForFreshPayload(3e3);if(!e?.ok)return void console.warn("[AB] no payload");const t=new Date,a=e=>String(e).padStart(2,"0"),r=`pulse_backup_${t.getFullYear()}-${a(t.getMonth()+1)}-${a(t.getDate())}_${a(t.getHours())}-${a(t.getMinutes())}-${a(t.getSeconds())}.json`,s="data:application/json;base64,"+btoa(unescape(encodeURIComponent(e.json)));chrome.downloads.download({url:s,filename:r,conflictAction:"uniquify",saveAs:!1},(e=>{chrome.runtime.lastError,chrome.storage.local.set({ahLastBackup:Date.now()})})),await new Promise((e=>setTimeout(e,1e3)))}function getCfCookie(){return new Promise((e=>{chrome.cookies.get({url:"https://alpha.date",name:"cf_clearance",partitionKey:{topLevelSite:"https://alpha.date"}},(t=>{if(t?.value)return e(t.value);chrome.cookies.getAll({name:"cf_clearance"},(t=>e(t[0]?.value||"")))}))}))}async function doBotAction(e,t){const a=t?.tab?.id||null;await ensureEndpointLoaded(),e.json&&e.hash&&(lastPayload=e.json,lastHash=e.hash),lastHeaders={"AH-Auth":e.key,"AH-OperatorID":e.opId},chrome.storage.local.set({ahLastPayload:lastPayload,ahLastHash:lastHash,ahLastHeaders:lastHeaders});try{const t=ahUrl(ahEndpoint,{auth:lastHeaders["AH-Auth"],opId:lastHeaders["AH-OperatorID"],opEmail:operatorEmail,action:e.run,hash:lastHash}),r=await fetch(t,{method:"POST",body:lastPayload,keepalive:!0});if(403===r.status)return void purgeAuth();handleForbidden(r);extractExpSec(r);const s=await r.clone().json().catch((()=>({})));"Start"===e.run&&s.sid&&s.wss_url&&s.auth&&(wsActive=!0,connectWs(s.sid,s.auth,s.wss_url),a&&(lastSenderTabId=a,ensureWsPort(a)))}catch(e){console.warn("[botAction] network error",e)}"Start"===e.run?(wsActive=!0,requestPulseKeepAwake(),setBadge("Running"),chrome.storage.local.set({ahRunning:!0}),chrome.alarms.create("ahUpdate",{periodInMinutes:1}),startOnlineStatusLoop()):"Stop"===e.run&&(wsActive=!1,stopOnlineStatusLoop(),releasePulseKeepAwake(),wsPingTmr&&(clearInterval(wsPingTmr),wsPingTmr=null),ahWs&&(ahWs.close(),ahWs=null),setBadge("Stopped"),chrome.storage.local.set({ahRunning:!1}),chrome.alarms.clear("ahUpdate"))}async function doPostHelper(e){if(await ensureEndpointLoaded(),!e.jwt)return{error:"JWT отсутствует - войдите в оператор"};if(!e.key)return{error:"Пустой AUTH-KEY"};const t={authorization:"Bearer "+e.jwt,"content-type":"application/json"};let a;try{const e=await alphaFetchViaAnyTab("/api/operator/profiles",{method:"GET",headers:t}),r=(new TextDecoder).decode(b64ToUint8(e.bodyBase64||""));if(a=JSON.parse(r),!Array.isArray(a)||!a.length)return{error:"Анкет нет"}}catch(e){return{error:"Ошибка fetch profiles: "+e}}const{external_id:r,id:s}=a[0];let o=null,n=[];try{const e=await alphaFetchViaAnyTab(`/api/sender/inviteList?external_id=${r}&mail_type=Chat`,{method:"GET",headers:t}),a=(new TextDecoder).decode(b64ToUint8(e.bodyBase64||""));n=Array.isArray(JSON.parse(a))?JSON.parse(a):[]}catch(e){n=[]}try{const e=(Array.isArray(n)?n:[]).filter((e=>"string"==typeof e?.message_content&&"test"===e.message_content.trim().toLowerCase()));e.length&&await Promise.allSettled(e.map((e=>alphaFetchViaAnyTab(`/api/sender/deleteInvite?external_id=${r}&id=${e.id}&mail_type=Chat`,{method:"GET",headers:t}))))}catch{}try{const e=(Array.isArray(n)?n:[]).find((e=>e.operator_id));e&&(o=e.operator_id)}catch{}if(!o)try{const e=JSON.stringify({external_id:r,woman_id:s,message_content:"test",message_type:"SENT_TEXT",sender_type:"chat"}),a=await alphaFetchViaAnyTab("/api/sender/addInvite",{method:"POST",headers:t,bodyStr:e}),n=(new TextDecoder).decode(b64ToUint8(a.bodyBase64||"")),i=JSON.parse(n)||{},c=Array.isArray(i?.inviteList)?i.inviteList:[],l=c.find((e=>e.operator_id));l&&(o=l.operator_id);const h=c.find((e=>"string"==typeof e?.message_content&&"test"===e.message_content.trim().toLowerCase()));if(h?.id)try{await alphaFetchViaAnyTab(`/api/sender/deleteInvite?external_id=${r}&id=${h.id}&mail_type=Chat`,{method:"GET",headers:t})}catch{}}catch{}if(!o)return{error:"operator_id не найден (inviteList/addInvite)"};try{const t=ahUrl(ahEndpoint,{auth:e.key,opId:o,opEmail:operatorEmail,action:"Init",hash:""}),a=await fetch(t,{method:"POST"});return extractExpSec(a),{status:a.status,statusText:a.statusText,body:await a.text(),origin:"ok"===a.headers.get("AH-Origin"),operator_id:o}}catch(e){return{error:"Ошибка POST Init: "+e}}}function handleToggleAuto(e){chrome.storage.local.get(["ahAutoBackup","ahLastBackup"],(t=>{if(e){const e={ahAutoBackup:!0};t.ahAutoBackup||(e.ahLastBackup=Date.now()),chrome.storage.local.set(e)}else chrome.storage.local.set({ahAutoBackup:!1})}))}async function handleGetCookies(e){try{e({cf:await getCfCookie()})}catch{e({cf:""})}}async function handleBotAction(e,t,a){try{const authState=await requirePulsarAuth(e&&e.key||null);if(!authState.ok)return void a({ok:!1,error:authState.error});await doBotAction(e,t),a({ok:!0})}catch(e){a({ok:!1,error:String(e)})}}async function handlePostHelper(e,t){try{t(await doPostHelper(e))}catch(e){t({error:String(e)})}}
async function handleGetAdminLogs(message, sendResponse){
  try{
    await ensureEndpointLoaded();
    const base = typeof message?.baseHost === "string" && message.baseHost.trim() ? message.baseHost.trim() : ahEndpoint;
    const normalizedBase = base.endsWith("/") ? base : base + "/";
    const url = new URL("admin/logs", normalizedBase);
    const operatorId = String(message?.operatorId || "").trim();
    if (operatorId) url.searchParams.set("operatorId", operatorId);
    const limit = Number(message?.limit || 0) || 0;
    if (limit > 0) url.searchParams.set("limit", String(limit));
    const res = await fetch(url.toString(), { method: "GET", cache: "no-store", credentials: "omit" });
    const json = await res.json().catch(() => ({}));
    sendResponse({ ok: res.ok, status: res.status, json });
  } catch (err) {
    sendResponse({ ok: false, error: String(err && err.message || err) });
  }
}
const STATS_RESET_ALARM="ahStatsReset";
// stats are stored on the server; extension keeps only an in-memory snapshot
function scheduleStatsReset(){try{chrome.alarms.create(STATS_RESET_ALARM,{when:next2300ms()})}catch{}}
async function mergeDailyStats(e){try{e&&"object"==typeof e&&(lastStatsDaily=e)}catch{}}
chrome.runtime.onStartup.addListener((async()=>{scheduleStatsReset();const e=await new Promise((e=>chrome.storage.local.get(["ahLastVersion","ahRunning","ahLastPayload","ahLastHash","ahLastHeaders","ahOperatorEmail"],e)));operatorEmail=String(e&&e.ahOperatorEmail||"").trim();if(e.ahLastVersion!==EXT_VERSION&&(chrome.storage.local.set({ahLastVersion:EXT_VERSION}),e.ahRunning))return void await doBotAction({run:"Stop",json:e.ahLastPayload||"",hash:e.ahLastHash||"",key:(e.ahLastHeaders||{})["AH-Auth"]||"",opId:(e.ahLastHeaders||{})["AH-OperatorID"]||""},{});const t=await new Promise((e=>chrome.storage.local.get(["ahRunning","ahLastPayload","ahLastHash","ahLastHeaders","ahBaseHost"],e)));if(setBadge(t.ahRunning?"Running":"Stopped"),t.ahRunning?(requestPulseKeepAwake(),startOnlineStatusLoop()):(stopOnlineStatusLoop(),releasePulseKeepAwake()),ahEndpoint=t.ahBaseHost||DEFAULT_ENDPOINT,/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/?$/i.test(String(ahEndpoint||""))&&(ahEndpoint=DEFAULT_ENDPOINT,chrome.storage.local.set({ahBaseHost:DEFAULT_ENDPOINT})),!t.ahRunning)return;lastPayload=t.ahLastPayload||"",lastHash=t.ahLastHash||"",lastHeaders=t.ahLastHeaders||{};const a=await askForFreshPayload();if(a?.ok&&(lastPayload=a.json,lastHash=a.hash,chrome.storage.local.set({ahLastPayload:lastPayload,ahLastHash:lastHash})),!lastHeaders?.["AH-Auth"])return void chrome.alarms.create("ahUpdate",{periodInMinutes:1});const r=await getBearerToken(),s=await getProfiles(r);if(!s.some((e=>1===Number(e.online??e.is_online))))return void chrome.alarms.create("ahUpdate",{periodInMinutes:1});const o=await fetchCurrentOperatorId(r,s);if(o&&o!==lastHeaders["AH-OperatorID"])chrome.alarms.create("ahUpdate",{periodInMinutes:1});else{try{const e=ahUrl(ahEndpoint,{auth:lastHeaders["AH-Auth"],opId:lastHeaders["AH-OperatorID"],opEmail:operatorEmail,action:"Start",hash:lastHash}),t=await fetch(e,{method:"POST",body:lastPayload,keepalive:!0});handleForbidden(t),extractExpSec(t);let a={};try{a=await t.clone().json()}catch{}a.sid&&a.wss_url&&a.auth&&(wsActive=!0,requestPulseKeepAwake(),connectWs(a.sid,a.auth,a.wss_url))}catch(e){console.warn("[onStartup]",e)}chrome.alarms.create("ahUpdate",{periodInMinutes:1})}})),chrome.alarms.onAlarm.addListener((async e=>{if(STATS_RESET_ALARM===e.name){scheduleStatsReset();return}if("ahUpdate"===e.name){if(!lastPayload||!lastHeaders?.["AH-Auth"]){const e=await new Promise((e=>chrome.storage.local.get(["ahLastPayload","ahLastHash","ahLastHeaders"],e)));lastPayload=e.ahLastPayload||"",lastHash=e.ahLastHash||"",lastHeaders=e.ahLastHeaders||{}}try{await sendUpdate()}catch(e){console.warn("[ahUpdate]",e)}}})),chrome.action.onClicked.addListener((e=>{e.id&&chrome.tabs.sendMessage(e.id,"openModal",(()=>{chrome.runtime.lastError}))})),chrome.runtime.onMessage.addListener(((e,t,a)=>{if("token"===e?.type)return bearerToken=e.value||null,void chrome.storage.local.set({ahJwt:bearerToken});if("getCookies"===e)return handleGetCookies(a),!0;if("getStatsDaily"===e?.cmd)return a({ok:!0,stats:lastStatsDaily||{}}),!0;if("botAction"===e?.cmd)return handleBotAction(e,t,a),!0;if("getAdminLogs"===e?.cmd)return handleGetAdminLogs(e,a),!0;if("downloadDataUrl"===e?.cmd)return handleDownloadDataUrl(e,a),!0;if("setBadge"!==e?.cmd){if("saveEndpoint"!==e?.cmd)return"postHelper"===e?.cmd?(handlePostHelper(e,a),!0):void("toggleAutoBackup"!==e?.cmd?"resetAuth"!==e?.cmd||purgeAuth():handleToggleAuto(e.enable));switchEndpoint(e.ep)}else setBadge(e.state)}));const keepAlivePorts=new Set,wsKeepPorts=new Map;function ensureWsPort(e){wsKeepPorts.has(e)||chrome.tabs.get(e,(t=>{if(chrome.runtime.lastError||!t)return;if("complete"!==t.status)return;if(!/^https:\/\/alpha\.date\//i.test(t.url||""))return;let a=chrome.tabs.connect(e,{name:"AH_WS_KEEP"});!chrome.runtime.lastError&&a&&(wsKeepPorts.set(e,a),a.onMessage.addListener((()=>{})),a.onDisconnect.addListener((()=>{chrome.runtime.lastError,wsKeepPorts.delete(e)})))}))}chrome.tabs.onActivated.addListener((({tabId:e})=>{ensureWsPort(e)})),chrome.tabs.onUpdated.addListener(((e,t)=>{"complete"===t.status&&ensureWsPort(e)})),chrome.runtime.onConnect.addListener((e=>{"KEEP_ALIVE_AH"===e.name&&(keepAlivePorts.add(e),e.onMessage.addListener((()=>{})),e.onDisconnect.addListener((()=>{try{chrome.runtime.lastError}catch{}keepAlivePorts.delete(e)})))}));


function handleDownloadDataUrl(message, sendResponse){
  try{
    const url = String(message?.url || '').trim();
    if(!url) return void sendResponse({ok:!1,error:'Empty download URL'});
    const filename = String(message?.filename || 'Pulse/screenshot.jpg').trim() || 'Pulse/screenshot.jpg';
    chrome.downloads.download({
      url,
      filename,
      saveAs: !!message?.saveAs,
      conflictAction: 'uniquify'
    }, downloadId => {
      if(chrome.runtime.lastError) return void sendResponse({ok:!1,error:chrome.runtime.lastError.message||'Download failed'});
      sendResponse({ok:!0,downloadId});
    });
  }catch(error){
    sendResponse({ok:!1,error:String(error&&error.message||error)});
  }
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (!message || message.type !== 'PLACE_TIME_FETCH' || !message.url) return;
  (async () => {
    try {
      const res = await fetch(message.url, { method: 'GET' });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      sendResponse({ data });
    } catch (e) {
      sendResponse({ error: String(e && e.message || e) });
    }
  })();
  return true;
});




const PULSE_CONTEXT_MENU_UNICODE_PARENT = "pulseUnicodeParent";
const PULSE_CONTEXT_MENU_UNICODE_PREFIX = "pulseUnicode:";
const PULSE_CONTEXT_MENU_REPLACE_SELECTION = "pulseReplaceSelection";
const PULSE_CONTEXT_MENU_MEDIA_BLUR = "pulseMediaBlurToggle";
const PULSE_MEDIA_BLUR_KEY = "pulseMediaBlurClickMode";
const PULSE_UNICODE_MENU_STYLES = [
  ["sansbold", "Unicode → Bold"],
  ["sansitalic", "Unicode → Italic"],
  ["sansbolditalic", "Unicode → Bold Italic"],
  ["script", "Unicode → Script"],
  ["serifbold", "Unicode → Serif Bold"],
  ["monospace", "Unicode → Monospace"]
];

function pulseUnicodeMaps(){
  return {
    sansbold:{A:'𝗔',B:'𝗕',C:'𝗖',D:'𝗗',E:'𝗘',F:'𝗙',G:'𝗚',H:'𝗛',I:'𝗜',J:'𝗝',K:'𝗞',L:'𝗟',M:'𝗠',N:'𝗡',O:'𝗢',P:'𝗣',Q:'𝗤',R:'𝗥',S:'𝗦',T:'𝗧',U:'𝗨',V:'𝗩',W:'𝗪',X:'𝗫',Y:'𝗬',Z:'𝗭',a:'𝗮',b:'𝗯',c:'𝗰',d:'𝗱',e:'𝗲',f:'𝗳',g:'𝗴',h:'𝗵',i:'𝗶',j:'𝗷',k:'𝗸',l:'𝗹',m:'𝗺',n:'𝗻',o:'𝗼',p:'𝗽',q:'𝗾',r:'𝗿',s:'𝘀',t:'𝘁',u:'𝘂',v:'𝘃',w:'𝘄',x:'𝘅',y:'𝘆',z:'𝘇'},
    sansitalic:{A:'𝘈',B:'𝘉',C:'𝘊',D:'𝘋',E:'𝘌',F:'𝘍',G:'𝘎',H:'𝘏',I:'𝘐',J:'𝘑',K:'𝘒',L:'𝘓',M:'𝘔',N:'𝘕',O:'𝘖',P:'𝘗',Q:'𝘘',R:'𝘙',S:'𝘚',T:'𝘛',U:'𝘜',V:'𝘝',W:'𝘞',X:'𝘟',Y:'𝘠',Z:'𝘡',a:'𝘢',b:'𝘣',c:'𝘤',d:'𝘥',e:'𝘦',f:'𝘧',g:'𝘨',h:'𝘩',i:'𝘪',j:'𝘫',k:'𝘬',l:'𝘭',m:'𝘮',n:'𝘯',o:'𝘰',p:'𝘱',q:'𝘲',r:'𝘳',s:'𝘴',t:'𝘵',u:'𝘶',v:'𝘷',w:'𝘸',x:'𝘹',y:'𝘺',z:'𝘻'},
    sansbolditalic:{A:'𝘼',B:'𝘽',C:'𝘾',D:'𝘿',E:'𝙀',F:'𝙁',G:'𝙂',H:'𝙃',I:'𝙄',J:'𝙅',K:'𝙆',L:'𝙇',M:'𝙈',N:'𝙉',O:'𝙊',P:'𝙋',Q:'𝙌',R:'𝙍',S:'𝙎',T:'𝙏',U:'𝙐',V:'𝙑',W:'𝙒',X:'𝙓',Y:'𝙔',Z:'𝙕',a:'𝙖',b:'𝙗',c:'𝙘',d:'𝙙',e:'𝙚',f:'𝙛',g:'𝙜',h:'𝙝',i:'𝙞',j:'𝙟',k:'𝙠',l:'𝙡',m:'𝙢',n:'𝙣',o:'𝙤',p:'𝙥',q:'𝙦',r:'𝙧',s:'𝙨',t:'𝙩',u:'𝙪',v:'𝙫',w:'𝙬',x:'𝙭',y:'𝙮',z:'𝙯'},
    script:{A:'𝓐',B:'𝓑',C:'𝓒',D:'𝓓',E:'𝓔',F:'𝓕',G:'𝓖',H:'𝓗',I:'𝓘',J:'𝓙',K:'𝓚',L:'𝓛',M:'𝓜',N:'𝓝',O:'𝓞',P:'𝓟',Q:'𝓠',R:'𝓡',S:'𝓢',T:'𝓣',U:'𝓤',V:'𝓥',W:'𝓦',X:'𝓧',Y:'𝓨',Z:'𝓩',a:'𝓪',b:'𝓫',c:'𝓬',d:'𝓭',e:'𝓮',f:'𝓯',g:'𝓰',h:'𝓱',i:'𝓲',j:'𝓳',k:'𝓴',l:'𝓵',m:'𝓶',n:'𝓷',o:'𝓸',p:'𝓹',q:'𝓺',r:'𝓻',s:'𝓼',t:'𝓽',u:'𝓾',v:'𝓿',w:'𝔀',x:'𝔁',y:'𝔂',z:'𝔃'},
    serifbold:{A:'𝐀',B:'𝐁',C:'𝐂',D:'𝐃',E:'𝐄',F:'𝐅',G:'𝐆',H:'𝐇',I:'𝐈',J:'𝐉',K:'𝐊',L:'𝐋',M:'𝐌',N:'𝐍',O:'𝐎',P:'𝐏',Q:'𝐐',R:'𝐑',S:'𝐒',T:'𝐓',U:'𝐔',V:'𝐕',W:'𝐖',X:'𝐗',Y:'𝐘',Z:'𝐙',a:'𝐚',b:'𝐛',c:'𝐜',d:'𝐝',e:'𝐞',f:'𝐟',g:'𝐠',h:'𝐡',i:'𝐢',j:'𝐣',k:'𝐤',l:'𝐥',m:'𝐦',n:'𝐧',o:'𝐨',p:'𝐩',q:'𝐪',r:'𝐫',s:'𝐬',t:'𝐭',u:'𝐮',v:'𝐯',w:'𝐰',x:'𝐱',y:'𝐲',z:'𝐳'},
    monospace:{A:'𝙰',B:'𝙱',C:'𝙲',D:'𝙳',E:'𝙴',F:'𝙵',G:'𝙶',H:'𝙷',I:'𝙸',J:'𝙹',K:'𝙺',L:'𝙻',M:'𝙼',N:'𝙽',O:'𝙾',P:'𝙿',Q:'𝚀',R:'𝚁',S:'𝚂',T:'𝚃',U:'𝚄',V:'𝚅',W:'𝚆',X:'𝚇',Y:'𝚈',Z:'𝚉',a:'𝚊',b:'𝚋',c:'𝚌',d:'𝚍',e:'𝚎',f:'𝚏',g:'𝚐',h:'𝚑',i:'𝚒',j:'𝚓',k:'𝚔',l:'𝚕',m:'𝚖',n:'𝚗',o:'𝚘',p:'𝚙',q:'𝚚',r:'𝚛',s:'𝚜',t:'𝚝',u:'𝚞',v:'𝚟',w:'𝚠',x:'𝚡',y:'𝚢',z:'𝚣'}
  };
}

function pulseConvertSelectionToUnicode(style){
  const maps = {
    sansbold:{A:'𝗔',B:'𝗕',C:'𝗖',D:'𝗗',E:'𝗘',F:'𝗙',G:'𝗚',H:'𝗛',I:'𝗜',J:'𝗝',K:'𝗞',L:'𝗟',M:'𝗠',N:'𝗡',O:'𝗢',P:'𝗣',Q:'𝗤',R:'𝗥',S:'𝗦',T:'𝗧',U:'𝗨',V:'𝗩',W:'𝗪',X:'𝗫',Y:'𝗬',Z:'𝗭',a:'𝗮',b:'𝗯',c:'𝗰',d:'𝗱',e:'𝗲',f:'𝗳',g:'𝗴',h:'𝗵',i:'𝗶',j:'𝗷',k:'𝗸',l:'𝗹',m:'𝗺',n:'𝗻',o:'𝗼',p:'𝗽',q:'𝗾',r:'𝗿',s:'𝘀',t:'𝘁',u:'𝘂',v:'𝘃',w:'𝘄',x:'𝘅',y:'𝘆',z:'𝘇'},
    sansitalic:{A:'𝘈',B:'𝘉',C:'𝘊',D:'𝘋',E:'𝘌',F:'𝘍',G:'𝘎',H:'𝘏',I:'𝘐',J:'𝘑',K:'𝘒',L:'𝘓',M:'𝘔',N:'𝘕',O:'𝘖',P:'𝘗',Q:'𝘘',R:'𝘙',S:'𝘚',T:'𝘛',U:'𝘜',V:'𝘝',W:'𝘞',X:'𝘟',Y:'𝘠',Z:'𝘡',a:'𝘢',b:'𝘣',c:'𝘤',d:'𝘥',e:'𝘦',f:'𝘧',g:'𝘨',h:'𝘩',i:'𝘪',j:'𝘫',k:'𝘬',l:'𝘭',m:'𝘮',n:'𝘯',o:'𝘰',p:'𝘱',q:'𝘲',r:'𝘳',s:'𝘴',t:'𝘵',u:'𝘶',v:'𝘷',w:'𝘸',x:'𝘹',y:'𝘺',z:'𝘻'},
    sansbolditalic:{A:'𝘼',B:'𝘽',C:'𝘾',D:'𝘿',E:'𝙀',F:'𝙁',G:'𝙂',H:'𝙃',I:'𝙄',J:'𝙅',K:'𝙆',L:'𝙇',M:'𝙈',N:'𝙉',O:'𝙊',P:'𝙋',Q:'𝙌',R:'𝙍',S:'𝙎',T:'𝙏',U:'𝙐',V:'𝙑',W:'𝙒',X:'𝙓',Y:'𝙔',Z:'𝙕',a:'𝙖',b:'𝙗',c:'𝙘',d:'𝙙',e:'𝙚',f:'𝙛',g:'𝙜',h:'𝙝',i:'𝙞',j:'𝙟',k:'𝙠',l:'𝙡',m:'𝙢',n:'𝙣',o:'𝙤',p:'𝙥',q:'𝙦',r:'𝙧',s:'𝙨',t:'𝙩',u:'𝙪',v:'𝙫',w:'𝙬',x:'𝙭',y:'𝙮',z:'𝙯'},
    script:{A:'𝓐',B:'𝓑',C:'𝓒',D:'𝓓',E:'𝓔',F:'𝓕',G:'𝓖',H:'𝓗',I:'𝓘',J:'𝓙',K:'𝓚',L:'𝓛',M:'𝓜',N:'𝓝',O:'𝓞',P:'𝓟',Q:'𝓠',R:'𝓡',S:'𝓢',T:'𝓣',U:'𝓤',V:'𝓥',W:'𝓦',X:'𝓧',Y:'𝓨',Z:'𝓩',a:'𝓪',b:'𝓫',c:'𝓬',d:'𝓭',e:'𝓮',f:'𝓯',g:'𝓰',h:'𝓱',i:'𝓲',j:'𝓳',k:'𝓴',l:'𝓵',m:'𝓶',n:'𝓷',o:'𝓸',p:'𝓹',q:'𝓺',r:'𝓻',s:'𝓼',t:'𝓽',u:'𝓾',v:'𝓿',w:'𝔀',x:'𝔁',y:'𝔂',z:'𝔃'},
    serifbold:{A:'𝐀',B:'𝐁',C:'𝐂',D:'𝐃',E:'𝐄',F:'𝐅',G:'𝐆',H:'𝐇',I:'𝐈',J:'𝐉',K:'𝐊',L:'𝐋',M:'𝐌',N:'𝐍',O:'𝐎',P:'𝐏',Q:'𝐐',R:'𝐑',S:'𝐒',T:'𝐓',U:'𝐔',V:'𝐕',W:'𝐖',X:'𝐗',Y:'𝐘',Z:'𝐙',a:'𝐚',b:'𝐛',c:'𝐜',d:'𝐝',e:'𝐞',f:'𝐟',g:'𝐠',h:'𝐡',i:'𝐢',j:'𝐣',k:'𝐤',l:'𝐥',m:'𝐦',n:'𝐧',o:'𝐨',p:'𝐩',q:'𝐪',r:'𝐫',s:'𝐬',t:'𝐭',u:'𝐮',v:'𝐯',w:'𝐰',x:'𝐱',y:'𝐲',z:'𝐳'},
    monospace:{A:'𝙰',B:'𝙱',C:'𝙲',D:'𝙳',E:'𝙴',F:'𝙵',G:'𝙶',H:'𝙷',I:'𝙸',J:'𝙹',K:'𝙺',L:'𝙻',M:'𝙼',N:'𝙽',O:'𝙾',P:'𝙿',Q:'𝚀',R:'𝚁',S:'𝚂',T:'𝚃',U:'𝚄',V:'𝚅',W:'𝚆',X:'𝚇',Y:'𝚈',Z:'𝚉',a:'𝚊',b:'𝚋',c:'𝚌',d:'𝚍',e:'𝚎',f:'𝚏',g:'𝚐',h:'𝚑',i:'𝚒',j:'𝚓',k:'𝚔',l:'𝚕',m:'𝚖',n:'𝚗',o:'𝚘',p:'𝚙',q:'𝚚',r:'𝚛',s:'𝚜',t:'𝚝',u:'𝚞',v:'𝚟',w:'𝚠',x:'𝚡',y:'𝚢',z:'𝚣'}
  };
  const map = maps[style] || maps.sansbold;
  const convert = text => String(text || "").split("").map(ch => map[ch] || ch).join("");
  const active = document.activeElement;
  const isTextInput = active && (
    active.tagName === "TEXTAREA" ||
    (active.tagName === "INPUT" && /^(text|search|url|tel|password|email)$/i.test(active.type || "text"))
  );
  if(!isTextInput) return;
  const start = Number(active.selectionStart || 0);
  const end = Number(active.selectionEnd || 0);
  if(end <= start) return;
  const value = String(active.value || "");
  const converted = convert(value.slice(start, end));
  const nextValue = value.slice(0, start) + converted + value.slice(end);
  const proto = Object.getPrototypeOf(active);
  const desc = Object.getOwnPropertyDescriptor(proto, "value");
  if(desc && desc.set) desc.set.call(active, nextValue);
  else active.value = nextValue;
  try{ active.setSelectionRange(start, start + converted.length); }catch{}
  active.dispatchEvent(new Event("input", { bubbles: true }));
  active.dispatchEvent(new Event("change", { bubbles: true }));
}

function pulseReplaceSelectionWithStars(){
  const sel = window.getSelection && window.getSelection();
  if(!sel || sel.rangeCount===0) return;
  const range = sel.getRangeAt(0);
  if(!range || range.collapsed) return;
  const text = String(range.toString() || "");
  if(!text) return;
  const replacement = text.replace(/[^\s]/g, "*");
  const active = document.activeElement;
  const isTextInput = active && (
    active.tagName === "TEXTAREA" ||
    (active.tagName === "INPUT" && /^(text|search|url|tel|password|email)$/i.test(active.type || "text"))
  );
  if(isTextInput){
    const start = Number(active.selectionStart || 0);
    const end = Number(active.selectionEnd || 0);
    if(end <= start) return;
    const value = String(active.value || "");
    const nextValue = value.slice(0, start) + replacement + value.slice(end);
    const proto = Object.getPrototypeOf(active);
    const desc = Object.getOwnPropertyDescriptor(proto, "value");
    if(desc && desc.set) desc.set.call(active, nextValue);
    else active.value = nextValue;
    try{ active.setSelectionRange(start + replacement.length, start + replacement.length); }catch{}
    active.dispatchEvent(new Event("input", { bubbles: true }));
    active.dispatchEvent(new Event("change", { bubbles: true }));
    return;
  }
  try{
    range.deleteContents();
    range.insertNode(document.createTextNode(replacement));
    sel.removeAllRanges();
  }catch(err){
    console.warn("Pulse replace selection failed", err);
  }
}

async function pulseCanUseUnicode(expectedToken=null){try{const res=await requirePulsarAuth(expectedToken);return!!res&&!!res.ok}catch{return!1}}
async function pulseReadMediaBlurState(){
  try{
    const data = await readLocal([PULSE_MEDIA_BLUR_KEY]);
    return !!(data && data[PULSE_MEDIA_BLUR_KEY]);
  }catch{
    return false;
  }
}
function pulseSendMediaBlurMode(tabId, enabled){
  if(!tabId) return;
  try{
    chrome.tabs.sendMessage(tabId, { cmd: "pulseSetMediaBlurMode", enabled: !!enabled }, ()=>void chrome.runtime.lastError);
  }catch{}
}
async function pulseSyncContextMenus(){
  const [unicodeEnabled, mediaBlurEnabled] = await Promise.all([
    pulseCanUseUnicode(),
    pulseReadMediaBlurState()
  ]);
  try{
    chrome.contextMenus.removeAll(()=>{
      void chrome.runtime.lastError;
      chrome.contextMenus.create({
        id: PULSE_CONTEXT_MENU_MEDIA_BLUR,
        title: mediaBlurEnabled ? "Blur media: ON" : "Blur media: OFF",
        contexts: ["page", "image", "video"],
        documentUrlPatterns: ["https://alpha.date/*"]
      }, ()=>void chrome.runtime.lastError);
      if(!unicodeEnabled) return;
      chrome.contextMenus.create({
        id: PULSE_CONTEXT_MENU_UNICODE_PARENT,
        title: "Unicode",
        contexts: ["selection", "editable"],
        documentUrlPatterns: ["https://alpha.date/*"]
      }, ()=>void chrome.runtime.lastError);
      for (const [style, title] of PULSE_UNICODE_MENU_STYLES){
        chrome.contextMenus.create({
          id: PULSE_CONTEXT_MENU_UNICODE_PREFIX + style,
          parentId: PULSE_CONTEXT_MENU_UNICODE_PARENT,
          title,
          contexts: ["selection", "editable"],
          documentUrlPatterns: ["https://alpha.date/*"]
        }, ()=>void chrome.runtime.lastError);
      }
      chrome.contextMenus.create({
        id: PULSE_CONTEXT_MENU_REPLACE_SELECTION,
        title: "Replace with *'s",
        contexts: ["selection", "editable"],
        documentUrlPatterns: ["https://alpha.date/*"]
      }, ()=>void chrome.runtime.lastError);
    });
  }catch{}
}
try{ chrome.runtime.onInstalled.addListener(()=>{ void pulseSyncContextMenus(); }); }catch{}
try{ chrome.runtime.onStartup.addListener(()=>{ void pulseSyncContextMenus(); }); }catch{}
try{ chrome.storage.onChanged.addListener((changes, area)=>{ if(area!=="local") return; if(changes.pulsarAuth||changes.ahSet||changes[PULSE_MEDIA_BLUR_KEY]) void pulseSyncContextMenus(); }); }catch{}
try{ void pulseSyncContextMenus(); }catch{}
try{
  chrome.contextMenus.onClicked.addListener(async (info, tab)=>{
    if(typeof info.menuItemId !== "string") return;
    if(info.menuItemId === PULSE_CONTEXT_MENU_MEDIA_BLUR){
      const nextEnabled = !(await pulseReadMediaBlurState());
      try{ await chrome.storage.local.set({ [PULSE_MEDIA_BLUR_KEY]: nextEnabled }); }catch{}
      pulseSendMediaBlurMode(tab && tab.id, nextEnabled);
      void pulseSyncContextMenus();
      return;
    }
    if(!(tab && tab.id)) return;
    const auth=await requirePulsarAuth();
    if(!auth||!auth.ok){
      void pulseSyncContextMenus();
      return;
    }
    if(info.menuItemId === PULSE_CONTEXT_MENU_REPLACE_SELECTION){
      chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: pulseReplaceSelectionWithStars
      }).catch(err=>console.error("Pulse replace selection: executeScript failed", err));
      return;
    }
    if(!info.menuItemId.startsWith(PULSE_CONTEXT_MENU_UNICODE_PREFIX)) return;
    const style = info.menuItemId.slice(PULSE_CONTEXT_MENU_UNICODE_PREFIX.length);
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      func: pulseConvertSelectionToUnicode,
      args: [style]
    }).catch(err=>console.error('Pulse unicode selection: executeScript failed', err));
  });
}catch{}

// === Finder-context socket relay (page world -> content -> background) ===
const pulseFinderReadStorage = (key) => new Promise(resolve => chrome.storage.local.get([key], (r) => resolve(r ? r[key] : undefined)));
const pulseFinderSetStorage = (obj) => new Promise(resolve => chrome.storage.local.set(obj, resolve));
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (!message || typeof message !== 'object') return;
  if (message.type === 'TOKEN_UPDATED') {
    chrome.storage.local.set({ token: message.token });
    sendResponse && sendResponse({ ok: true });
    return true;
  }
  if (message.type === 'PULSE_FINDER_PROFILES') {
    chrome.storage.local.set({ profiles: Array.isArray(message.profiles) ? message.profiles : [], pulseFinderProfilesUpdatedAt: new Date().toISOString() });
    sendResponse && sendResponse({ ok: true });
    return true;
  }
  if (message.type === 'PULSE_FINDER_SOCKET_STATUS') {
    chrome.storage.local.set({ pulseSocketState: { status: message.status, extra: message.extra || null, at: new Date().toISOString() } });
    sendResponse && sendResponse({ ok: true });
    return true;
  }
  if (message.type === 'PULSE_FINDER_SOCKET_CHANNELS') {
    chrome.storage.local.set({ pulseSocketChannels: Array.isArray(message.channels) ? message.channels : [] });
    sendResponse && sendResponse({ ok: true });
    return true;
  }
  if (message.type === 'PULSE_FINDER_ERROR') {
    chrome.storage.local.set({ pulseFinderLastError: { scope: message.scope || 'unknown', message: message.message || '', at: new Date().toISOString() } });
    sendResponse && sendResponse({ ok: true });
    return true;
  }
  if (message.type === 'PULSE_FINDER_SOCKET_EVENT') {
    (async () => {
      const payload = message.payload || null;
      const channel = message.channel || '';
      const profiles = await pulseFinderReadStorage('profiles') || [];
      const externalId = String(channel).replace('counters_profile_', '');
      const profile = Array.isArray(profiles) ? profiles.find((p) => String(p && p.external_id) === externalId) : null;
      const messageObj = payload && payload.message_object ? payload.message_object : null;
      const profileExternalIdFromChannel = String(externalId || '').trim();
      const senderExternalId = (() => {
        try {
          const ids = [
            payload && (payload.sender_external_id || payload.senderExternalId),
            payload && (payload.recipient_external_id || payload.recipientExternalId),
            payload && (payload.sender_id || payload.senderId),
            payload && (payload.recipient_id || payload.recipientId),
            messageObj && (messageObj.sender_external_id || messageObj.senderExternalId),
            messageObj && (messageObj.recipient_external_id || messageObj.recipientExternalId),
            messageObj && (messageObj.sender_id || messageObj.senderId),
            messageObj && (messageObj.recipient_id || messageObj.recipientId),
          ].map((v) => String(v || '').trim()).filter(Boolean);
          const uniq = Array.from(new Set(ids));
          const filtered = uniq.filter((id) => id !== profileExternalIdFromChannel);
          if (filtered.length === 1) return filtered[0];
          const s = String((payload && (payload.sender_external_id || payload.senderExternalId || payload.sender_id || payload.senderId)) || (messageObj && (messageObj.sender_external_id || messageObj.senderExternalId || messageObj.sender_id || messageObj.senderId)) || '').trim();
          if (s && s !== profileExternalIdFromChannel) return s;
          const r = String((payload && (payload.recipient_external_id || payload.recipientExternalId || payload.recipient_id || payload.recipientId)) || (messageObj && (messageObj.recipient_external_id || messageObj.recipientExternalId || messageObj.recipient_id || messageObj.recipientId)) || '').trim();
          if (r && r !== profileExternalIdFromChannel) return r;
          return filtered[0] || s || r || null;
        } catch {
          return payload && (payload.sender_external_id || (messageObj && (messageObj.sender_external_id || messageObj.sender_id)) || null);
        }
      })();
      const messageType = payload && (payload.message_type || (messageObj && messageObj.message_type) || payload.action || payload.event_type || payload.type || null);
      let derivedType = null;
      if ((payload && payload.action === 'liked') || messageType === 'SENT_LIKE') derivedType = 'like_reply';
      else if (messageType === 'SENT_WINK') {
        const content = String((messageObj && messageObj.message_content) || payload.message_content || '');
        derivedType = content === 'Send a wink 😉' ? 'wink_reply' : 'free_message';
      }
      const profileEventKey = profile && profile.id || externalId || 'x';
      const messageEventKey = (messageObj && (messageObj.id || messageObj.chat_uid || messageObj.chat_id)) || (payload && (payload.chat_uid || payload.chat_id || payload.id || payload.event_id)) || (Date.now().toString(36) + Math.random().toString(36).slice(2, 7));
      const eventKey = `${profileEventKey}_${senderExternalId || 'x'}_${messageEventKey}_${messageType || 'unknown'}`;
      if (pulseSocketEventLocks.has(eventKey)) return;
      pulseSocketEventLocks.add(eventKey);
      try {
        const prev = await pulseFinderReadStorage('pulseSocketEvents');
        const list = Array.isArray(prev) ? prev : [];
        const existing = list.find((e) => e && e.eventKey === eventKey);
        if (pulseShouldSkipStoredSocketEvent(existing)) return;
        const entry = {
          eventKey,
          channel,
          profileId: profile && profile.id || null,
          profileExternalId: profile && profile.external_id || externalId || null,
          senderExternalId: senderExternalId || null,
          messageType: messageType || null,
          derivedType,
          payload,
          receivedAt: new Date().toISOString(),
          attempts: Number(existing?.attempts || 0) || 0,
          status: 'new'
        };
        await pulseFinderSetStorage({ pulseLastSocketEvent: entry });
        try {
          const processed = await pulseProcessSocketAuthorReplyEvent(entry);
          await pulseUpsertSocketEvent(processed);
        } catch (error) {
          const failedEntry = {
            ...entry,
            status: 'failed',
            attempts: (Number(entry.attempts || 0) || 0) + 1,
            finishedAt: new Date().toISOString(),
            error: error?.message || String(error)
          };
          await pulseUpsertSocketEvent(failedEntry);
          try { await pulseAuthorReplyLog('error', '[socket-authorreply] failed', `${eventKey} ${failedEntry.error}`); } catch {}
        }
      } finally {
        pulseSocketEventLocks.delete(eventKey);
      }
    })().finally(() => { try { sendResponse && sendResponse({ ok: true }); } catch {} });
    return true;
  }
  if (message.type === 'PULSE_FINDER_REFRESH_CONTEXT') {
    sendResponse && sendResponse({ ok: true });
    return true;
  }
});


// === Pulse author-reply methods adapted from local Pulse reference ===
const PULSE_AUTHORREPLY_CONFIG = {
  blockMs: 10 * 60 * 1000,
  blocksStorageKey: 'ahAuthorReplyBlocks',
  logStorageKey: 'pulseAuthorReplyLog'
};
const pulseSocketEventLocks = new Set();

const PULSE_AUTO_KEYS = [
  'Like',
  'View',
  'Wink',
  'Post',
  'Tell me about yourself',
  'How your day going?',
  'Dont you mind talking bit?',
  'What are you up to?',
];
const PULSE_AUTO_KEY_SET = new Set(PULSE_AUTO_KEYS.map(pulseNormKey));
const PULSE_REAL_CLIENT_MESSAGE_TYPES = new Set([
  'SENT_TEXT',
  'SENT_IMAGE',
  'SENT_VIDEO',
  'SENT_GIF',
  'SENT_STICKER',
  'SENT_AUDIO',
  'SENT_VOICE',
  'SENT_FILE',
]);
const pulseAuthorReplyTempBlocks = new Map();
const pulseAuthorReplyPending = new Set();
let pulseAuthorReplyBlocksLoaded = false;
let pulseAuthorReplyBlocksPromise = null;
function pulseNormKey(value) {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/[\s\u00A0]+/g, ' ')
    .replace(/[?!.]+$/g, '');
}
function pulseParsePayloadString(rawPayload) {
  if (typeof rawPayload !== 'string' || !rawPayload.trim()) return {};
  try { return JSON.parse(rawPayload); } catch { return {}; }
}
function pulseIsLikesEnabled(rawPayload) {
  const payload = pulseParsePayloadString(rawPayload);
  return !!payload?.settings?.likes;
}
function pulseIsAuthorReplyDistance2mEnabled(rawPayload) {
  const payload = pulseParsePayloadString(rawPayload);
  return !!payload?.settings?.authorReplyDistance2m;
}
function pulseIsAuthorReplyLikeFirstEnabled(rawPayload) {
  const payload = pulseParsePayloadString(rawPayload);
  return !!payload?.settings?.authorReplyLikeFirst;
}
const PULSE_AUTHOR_REPLY_DELAY_STEPS = [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50];
function pulseNormalizeAuthorReplyDelaySec(rawPayload) {
  const payload = pulseParsePayloadString(rawPayload);
  let value = Number(payload?.settings?.authorReplyDelaySec);
  if (!Number.isFinite(value)) return 1;
  value = Math.trunc(value);
  if (value <= 1) return 1;
  if (value >= 50) return 50;
  let best = PULSE_AUTHOR_REPLY_DELAY_STEPS[0];
  let bestDiff = Math.abs(value - best);
  for (const step of PULSE_AUTHOR_REPLY_DELAY_STEPS) {
    const diff = Math.abs(value - step);
    if (diff < bestDiff) {
      best = step;
      bestDiff = diff;
    }
  }
  return best;
}
function pulseGetAuthorReplyDelayMs(rawPayload) {
  return pulseNormalizeAuthorReplyDelaySec(rawPayload) * 1000;
}
async function pulseIsAuthorReplyGloballyAllowed() {
  try {
    const data = await new Promise((resolve) => chrome.storage.local.get(['ahRunning', 'ahSet'], resolve));
    if (data?.ahRunning === false) return false;
    if (data?.ahSet && typeof data.ahSet === 'object' && data.ahSet.running === false) return false;
    return !!data?.ahRunning;
  } catch {
    return false;
  }
}
function pulseNormalizeTemplateItem(item) {
  if (typeof item === 'string') {
    const text = item.trim();
    return text ? { text, image: null, picFirst: false } : null;
  }
  if (!item || typeof item !== 'object') return null;
  const text = (
    (typeof item.text === 'string' ? item.text : '') ||
    (typeof item.message_content === 'string' ? item.message_content : '') ||
    (typeof item.messageContent === 'string' ? item.messageContent : '') ||
    ''
  ).trim();
  const image = (
    (typeof item.image === 'string' ? item.image : '') ||
    (typeof item.img === 'string' ? item.img : '') ||
    (typeof item.photo === 'string' ? item.photo : '') ||
    (typeof item.link === 'string' ? item.link : '') ||
    ''
  ).trim() || null;
  const picFirst = !!(item.picFirst ?? item.pic_first ?? item.imageFirst ?? item.photoFirst ?? false);
  if (!text && !image) return null;
  return { text, image, picFirst };
}
function pulsePickTemplatesByKey(payload, extStr, key, limit = 3) {
  const want = pulseNormKey(key);
  const collectFromInvObj = (invObj) => {
    const out = [];
    if (!invObj || typeof invObj !== 'object') return out;
    for (const [cat, arr] of Object.entries(invObj)) {
      if (pulseNormKey(cat) !== want) continue;
      if (!Array.isArray(arr) || !arr.length) return out;
      for (const item of arr) {
        const normalized = pulseNormalizeTemplateItem(item);
        if (!normalized) continue;
        out.push(normalized);
        if (out.length >= limit) break;
      }
      return out;
    }
    return out;
  };
  const fromProfile = collectFromInvObj(payload?.[`invites${String(extStr)}`]);
  if (fromProfile.length) return fromProfile;
  const fromGlobal = collectFromInvObj(payload?.invites);
  if (fromGlobal.length) return fromGlobal;
  return [];
}

const PULSE_DEDUPE_PATHS = new Set([
  '/api/chatList/chatHistory',
  '/api/chatList/lastMessage',
  '/api/chatList/userDetail',
  '/api/chatList/chatListByUserID',
]);
const PULSE_DEDUPE_CACHE = new Map();
const PULSE_DEDUPE_MAX_ENTRIES = 120;
function pulseCloneJsonLike(value) {
  if (value == null) return value;
  try {
    return structuredClone(value);
  } catch {
    return JSON.parse(JSON.stringify(value));
  }
}
function pulseStableSortValue(value) {
  if (Array.isArray(value)) return value.map(pulseStableSortValue);
  if (value && typeof value === 'object') {
    return Object.keys(value).sort().reduce((acc, key) => {
      acc[key] = pulseStableSortValue(value[key]);
      return acc;
    }, {});
  }
  return value;
}
function pulseMakeDedupeKey(path, method, headers = {}, body = null) {
  const normalizedPath = String(path || '').trim();
  if (!PULSE_DEDUPE_PATHS.has(normalizedPath)) return '';
  const authHeader = String(headers.authorization || headers.Authorization || '').trim();
  const normalizedMethod = String(method || 'POST').trim().toUpperCase();
  const normalizedBody = body == null ? '' : JSON.stringify(pulseStableSortValue(body));
  return `${normalizedMethod}::${normalizedPath}::${authHeader}::${normalizedBody}`;
}
function pulseGetDedupeTtlMs(path) {
  return path === '/api/chatList/chatHistory' ? 5000 : 2500;
}
function pulsePruneDedupeCache(now = Date.now()) {
  for (const [key, entry] of PULSE_DEDUPE_CACHE.entries()) {
    const ttl = Number(entry?.ttlMs) || 0;
    const ts = Number(entry?.ts) || 0;
    if (!entry?.promise && ttl > 0 && ts > 0 && now - ts > ttl) {
      PULSE_DEDUPE_CACHE.delete(key);
    }
  }
  if (PULSE_DEDUPE_CACHE.size <= PULSE_DEDUPE_MAX_ENTRIES) return;
  const ordered = Array.from(PULSE_DEDUPE_CACHE.entries()).sort((a, b) => (Number(a[1]?.ts) || 0) - (Number(b[1]?.ts) || 0));
  while (ordered.length && PULSE_DEDUPE_CACHE.size > PULSE_DEDUPE_MAX_ENTRIES) {
    const [oldestKey] = ordered.shift();
    PULSE_DEDUPE_CACHE.delete(oldestKey);
  }
}
async function pulseRunDedupeJson(path, method, headers, body, factory) {
  const key = pulseMakeDedupeKey(path, method, headers, body);
  if (!key) return await factory();
  const now = Date.now();
  const ttlMs = pulseGetDedupeTtlMs(String(path || '').trim());
  pulsePruneDedupeCache(now);
  const existing = PULSE_DEDUPE_CACHE.get(key);
  if (existing) {
    if (existing.promise) {
      const shared = await existing.promise;
      return pulseCloneJsonLike(shared);
    }
    if (now - (Number(existing.ts) || 0) <= ttlMs) {
      return pulseCloneJsonLike(existing.value);
    }
    PULSE_DEDUPE_CACHE.delete(key);
  }
  const promise = (async () => await factory())();
  PULSE_DEDUPE_CACHE.set(key, { promise, ts: now, ttlMs });
  try {
    const value = await promise;
    PULSE_DEDUPE_CACHE.set(key, { value: pulseCloneJsonLike(value), ts: Date.now(), ttlMs });
    pulsePruneDedupeCache();
    return pulseCloneJsonLike(value);
  } catch (error) {
    PULSE_DEDUPE_CACHE.delete(key);
    throw error;
  }
}

function pulseFilenameFromLink(link) {
  const raw = String(link || '').trim();
  if (!raw) return '';
  try {
    const parsed = new URL(raw);
    const name = (parsed.pathname.split('/').pop() || '').trim();
    return name || 'image.jpg';
  } catch {
    const clean = raw.split('?')[0].split('#')[0];
    return (clean.split('/').pop() || 'image.jpg').trim() || 'image.jpg';
  }
}
async function pulseApiJsonViaAlpha(path, method = 'POST', { headers = {}, body = null, timeoutMs = 20000 } = {}) {
  return await pulseRunDedupeJson(path, method, headers, body, async () => {
    const res = await alphaFetchViaAnyTab(path, {
      method,
      headers,
      bodyStr: body != null ? JSON.stringify(body) : null,
    }, timeoutMs);
    const text = (new TextDecoder).decode(b64ToUint8(res.bodyBase64 || ''));
    return text ? JSON.parse(text) : null;
  });
}
async function pulseSendChatMessageByExternalIds(senderExternalId, recipientExternalId, text, authHeader) {
  const responseJson = await pulseApiJsonViaAlpha('/api/chat/message', 'POST', {
    headers: {
      accept: 'application/json, text/plain, */*',
      authorization: authHeader,
      'content-type': 'application/json;charset=UTF-8',
      'x-requested-with': 'XMLHttpRequest',
    },
    body: {
      sender_id: Number(senderExternalId),
      recipient_id: Number(recipientExternalId),
      chance: true,
      filename: '',
      message_content: String(text || ''),
      message_type: 'SENT_TEXT',
    },
    timeoutMs: 20000,
  });
  if (!(responseJson?.status === true)) {
    const reason = responseJson?.message || responseJson?.error || responseJson?.response || 'chat send rejected';
    throw new Error(`[authorreply] chat send rejected ${String(reason)}`);
  }
  return responseJson;
}
async function pulseSendChatLikeByExternalIds(senderExternalId, recipientExternalId, senderName, authHeader, options = {}) {
  const chanceValue = Object.prototype.hasOwnProperty.call(options || {}, 'chance') ? !!options.chance : true;
  const responseJson = await pulseApiJsonViaAlpha('/api/chat/message', 'POST', {
    headers: {
      accept: 'application/json, text/plain, */*',
      authorization: authHeader,
      'content-type': 'application/json;charset=UTF-8',
      'x-requested-with': 'XMLHttpRequest',
    },
    body: {
      sender_id: Number(senderExternalId),
      recipient_id: Number(recipientExternalId),
      chance: chanceValue,
      filename: '',
      message_content: `${String(senderName || 'She').trim() || 'She'} liked your profile`,
      message_type: 'SENT_LIKE',
    },
    timeoutMs: 20000,
  });
  if (!(responseJson?.status === true)) {
    const reason = responseJson?.message || responseJson?.error || responseJson?.response || 'like send rejected';
    throw new Error(`[authorreply] like send rejected ${String(reason)}`);
  }
  return responseJson;
}
async function pulseSendChatImageByExternalIds(senderExternalId, recipientExternalId, imageLink, authHeader) {
  const link = String(imageLink || '').trim();
  const responseJson = await pulseApiJsonViaAlpha('/api/chat/message', 'POST', {
    headers: {
      accept: 'application/json, text/plain, */*',
      authorization: authHeader,
      'content-type': 'application/json;charset=UTF-8',
      'x-requested-with': 'XMLHttpRequest',
    },
    body: {
      sender_id: Number(senderExternalId),
      recipient_id: Number(recipientExternalId),
      chance: true,
      filename: pulseFilenameFromLink(link),
      message_content: link,
      message_type: 'SENT_IMAGE',
    },
    timeoutMs: 20000,
  });
  if (!(responseJson?.status === true)) {
    const reason = responseJson?.message || responseJson?.error || responseJson?.response || 'image send rejected';
    throw new Error(`[authorreply] image send rejected ${String(reason)}`);
  }
  return responseJson;
}
function pulseGetBlockKey(profileId, recipientExternalId) {
  const profileKey = String(profileId || '').trim();
  const recipientKey = String(recipientExternalId || '').trim();
  return profileKey && recipientKey ? `${profileKey}::${recipientKey}` : '';
}
async function pulseEnsureAuthorReplyBlocksLoaded() {
  if (pulseAuthorReplyBlocksLoaded) return;
  if (pulseAuthorReplyBlocksPromise) return pulseAuthorReplyBlocksPromise;
  pulseAuthorReplyBlocksPromise = new Promise((resolve) => {
    try {
      chrome.storage.local.get([PULSE_AUTHORREPLY_CONFIG.blocksStorageKey], (payload) => {
        const raw = payload?.[PULSE_AUTHORREPLY_CONFIG.blocksStorageKey];
        if (raw && typeof raw === 'object') {
          for (const [key, entry] of Object.entries(raw)) {
            const expiresAt = Number(entry?.expiresAt || entry || 0);
            if (Number.isFinite(expiresAt) && expiresAt > Date.now()) {
              pulseAuthorReplyTempBlocks.set(key, {
                expiresAt,
                profileId: String(entry?.profileId || key.split('::')[0] || '').trim(),
                recipientExternalId: String(entry?.recipientExternalId || key.split('::')[1] || '').trim(),
                source: String(entry?.source || 'authorreply').trim() || 'authorreply',
                replyKey: String(entry?.replyKey || '').trim(),
                chatUid: String(entry?.chatUid || '').trim(),
                at: Number(entry?.at || 0) || 0,
              });
            }
          }
        }
        pulseAuthorReplyBlocksLoaded = true;
        resolve();
      });
    } catch {
      pulseAuthorReplyBlocksLoaded = true;
      resolve();
    }
  }).finally(() => { pulseAuthorReplyBlocksPromise = null; });
  return pulseAuthorReplyBlocksPromise;
}
async function pulsePersistAuthorReplyBlocks() {
  const obj = {};
  const now = Date.now();
  for (const [key, entry] of pulseAuthorReplyTempBlocks.entries()) {
    if (!entry || Number(entry.expiresAt) <= now) continue;
    obj[key] = {
      expiresAt: Number(entry.expiresAt),
      profileId: String(entry.profileId || ''),
      recipientExternalId: String(entry.recipientExternalId || ''),
      source: String(entry.source || 'authorreply'),
      replyKey: String(entry.replyKey || ''),
      chatUid: String(entry.chatUid || ''),
      at: Number(entry.at || 0) || 0,
    };
  }
  await pulseFinderSetStorage({ [PULSE_AUTHORREPLY_CONFIG.blocksStorageKey]: obj });
}
function pulsePruneAuthorReplyBlocks() {
  const now = Date.now();
  let changed = false;
  for (const [key, entry] of pulseAuthorReplyTempBlocks.entries()) {
    if (!entry || Number(entry.expiresAt) <= now) {
      pulseAuthorReplyTempBlocks.delete(key);
      changed = true;
    }
  }
  if (changed) void pulsePersistAuthorReplyBlocks();
}
function pulseIsAuthorReplyBlocked(profileId, recipientExternalId) {
  pulsePruneAuthorReplyBlocks();
  const key = pulseGetBlockKey(profileId, recipientExternalId);
  if (!key) return false;
  const entry = pulseAuthorReplyTempBlocks.get(key);
  return !!(entry && Number(entry.expiresAt) > Date.now());
}
async function pulseAddAuthorReplyBlock(profileId, recipientExternalId, meta = {}) {
  await pulseEnsureAuthorReplyBlocksLoaded();
  const key = pulseGetBlockKey(profileId, recipientExternalId);
  if (!key) return false;
  pulseAuthorReplyTempBlocks.set(key, {
    expiresAt: Date.now() + PULSE_AUTHORREPLY_CONFIG.blockMs,
    profileId: String(profileId || '').trim(),
    recipientExternalId: String(recipientExternalId || '').trim(),
    source: String(meta?.source || 'authorreply'),
    replyKey: String(meta?.replyKey || '').trim(),
    chatUid: String(meta?.chatUid || '').trim(),
    at: Date.now(),
  });
  pulsePruneAuthorReplyBlocks();
  await pulsePersistAuthorReplyBlocks();
  return true;
}
function pulseGuessChanceEventType(lastMsg) {
  const mtRaw = String(lastMsg?.message_type ?? lastMsg?.messageType ?? lastMsg?.type ?? lastMsg?.event_type ?? '').toUpperCase();
  if (!mtRaw) return 'Chance';
  if (mtRaw.includes('WINK')) return 'Wink';
  if (mtRaw.includes('VIEW')) return 'View';
  if (mtRaw.includes('VISIT')) return 'View';
  if (mtRaw.includes('POST')) return 'LikePost';
  if (mtRaw.includes('LIKE')) return 'Like';
  return 'Chance';
}
function pulseMapAutoReplyKey(lastMsg) {
  const raw = String(lastMsg?.message_type ?? lastMsg?.messageType ?? lastMsg?.type ?? lastMsg?.event_type ?? '').trim();
  const nk = pulseNormKey(raw);
  if (PULSE_AUTO_KEY_SET.has(nk)) {
    for (const key of PULSE_AUTO_KEYS) {
      if (pulseNormKey(key) === nk) return key;
    }
  }
  const ev = pulseGuessChanceEventType(lastMsg);
  if (ev === 'LikePost') return 'Post';
  if (ev === 'Like' || ev === 'View' || ev === 'Wink') return ev;
  return null;
}
function pulseDetectReplyKeyFromSocketEntry(entry) {
  const payload = entry?.payload || {};
  const msg = payload?.message_object || {};
  const synthetic = {
    message_type: msg?.message_type || payload?.message_type || entry?.messageType || '',
    type: msg?.message_content || payload?.message_content || '',
    event_type: payload?.action || '',
  };
  let key = pulseMapAutoReplyKey(synthetic);
  if (key) return key;
  const content = String(msg?.message_content || payload?.message_content || '').trim();
  const nk = pulseNormKey(content);
  if (PULSE_AUTO_KEY_SET.has(nk)) {
    for (const k of PULSE_AUTO_KEYS) if (pulseNormKey(k) === nk) return k;
  }
  if (entry?.derivedType === 'like_reply') return 'Like';
  if (entry?.derivedType === 'wink_reply') return 'Wink';
  return null;
}
async function pulseAuthorReplyLog(level, message, extra = '') {
  const prev = await pulseFinderReadStorage(PULSE_AUTHORREPLY_CONFIG.logStorageKey);
  const arr = Array.isArray(prev) ? prev : [];
  const line = { at: new Date().toISOString(), level: String(level || 'info'), message: String(message || ''), extra: String(extra || '') };
  await pulseFinderSetStorage({ [PULSE_AUTHORREPLY_CONFIG.logStorageKey]: [...arr.slice(-99), line], pulseAuthorReplyLastLog: line });
}
function pulseShouldSkipStoredSocketEvent(entry) {
  const status = String(entry?.status || '').trim().toLowerCase();
  return status === 'sent' || status === 'skipped';
}
async function pulseUpsertSocketEvent(entry) {
  const prev = await pulseFinderReadStorage('pulseSocketEvents');
  const list = Array.isArray(prev) ? prev.filter((row) => row && row.eventKey !== entry?.eventKey) : [];
  const next = [...list.slice(-98), entry];
  await pulseFinderSetStorage({ pulseSocketEvents: next, pulseLastSocketEvent: entry });
  return entry;
}
function pulseIsAuthorReplyRetriableResult(result) {
  const skipped = String(result?.skipped || '').trim().toLowerCase();
  return skipped === 'send-failed';
}
function pulseIsAuthorReplyFinalSkip(result) {
  return !!(result && result.ok === false && typeof result.skipped === 'string' && !pulseIsAuthorReplyRetriableResult(result));
}
function pulseSleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function pulseProcessSocketAuthorReplyEvent(entry, maxAttempts = 3) {
  let lastResult = null;
  let lastError = null;
  const baseAttempts = Number(entry?.attempts || 0) || 0;
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    try {
      const result = await pulseAuthorReplyHandleSocketEvent(entry);
      lastResult = result;
      const processedEntry = {
        ...entry,
        attempts: baseAttempts + attempt,
        finishedAt: new Date().toISOString(),
      };
      if (result?.ok) {
        return { ...processedEntry, status: 'sent', result };
      }
      if (pulseIsAuthorReplyFinalSkip(result)) {
        return { ...processedEntry, status: 'skipped', result, skipped: result?.skipped || '' };
      }
      if (!pulseIsAuthorReplyRetriableResult(result) || attempt >= maxAttempts) {
        return { ...processedEntry, status: 'failed', result, skipped: result?.skipped || 'send-failed' };
      }
      await pulseAuthorReplyLog('warn', '[socket-authorreply] retry scheduled', `${entry?.eventKey || '?'} attempt=${attempt} skipped=${result?.skipped || 'send-failed'}`);
    } catch (error) {
      lastError = error;
      if (attempt >= maxAttempts) break;
      await pulseAuthorReplyLog('warn', '[socket-authorreply] retry scheduled', `${entry?.eventKey || '?'} attempt=${attempt} error=${error?.message || String(error)}`);
    }
    await pulseSleep(700 * attempt);
  }
  return {
    ...entry,
    status: 'failed',
    attempts: baseAttempts + maxAttempts,
    finishedAt: new Date().toISOString(),
    result: lastResult,
    error: lastError?.message || String(lastResult?.skipped || 'send-failed')
  };
}
function pulseHasRealClientMessageContent(row) {
  const type = String(row?.message_type || '').trim().toUpperCase();
  if (!PULSE_REAL_CLIENT_MESSAGE_TYPES.has(type)) return false;
  if (type === 'SENT_TEXT') return !!String(row?.message_content || '').trim();
  return !!String(row?.filename || row?.file_name || row?.message_content || '').trim();
}
function pulseIsRealCandidateChatMessage(row, candidateExternalId, profileExternalId) {
  const senderExternalId = String(row?.sender_external_id || row?.senderExternalId || '').trim();
  const recipientExternalId = String(row?.recipient_external_id || row?.recipientExternalId || '').trim();
  const normalizedCandidateExternalId = String(candidateExternalId || '').trim();
  const normalizedProfileExternalId = String(profileExternalId || '').trim();
  if (!normalizedCandidateExternalId || !normalizedProfileExternalId) return false;
  if (senderExternalId !== normalizedCandidateExternalId) return false;
  if (recipientExternalId && recipientExternalId !== normalizedProfileExternalId) return false;
  return pulseHasRealClientMessageContent(row);
}
function pulseIsRealProfileChatMessage(row, profileExternalId, candidateExternalId) {
  const senderExternalId = String(row?.sender_external_id || row?.senderExternalId || '').trim();
  const recipientExternalId = String(row?.recipient_external_id || row?.recipientExternalId || '').trim();
  const normalizedProfileExternalId = String(profileExternalId || '').trim();
  const normalizedCandidateExternalId = String(candidateExternalId || '').trim();
  if (!normalizedProfileExternalId || !normalizedCandidateExternalId) return false;
  if (senderExternalId !== normalizedProfileExternalId) return false;
  if (recipientExternalId && recipientExternalId !== normalizedCandidateExternalId) return false;
  return pulseHasRealClientMessageContent(row);
}
function pulseGetRowTimestampMs(row) {
  const raw = row?.date_created || row?.created_at || row?.updated_at || row?.date || 0;
  const ms = Date.parse(raw);
  return Number.isFinite(ms) ? ms : 0;
}
async function pulseGetLatestProfileMessageTimestampInChat(chatUid, authHeader, profileExternalId, candidateExternalId, maxPages = 20) {
  const normalizedChatUid = String(chatUid || '').trim();
  if (!normalizedChatUid) return 0;
  for (let page = 1; page <= maxPages; page += 1) {
    const responseJson = await pulseApiJsonViaAlpha('/api/chatList/chatHistory', 'POST', {
      headers: {
        accept: 'application/json, text/plain, */*',
        authorization: authHeader,
        'content-type': 'application/json;charset=UTF-8',
        'x-requested-with': 'XMLHttpRequest',
      },
      body: {
        chat_id: normalizedChatUid,
        page,
      },
      timeoutMs: 20000,
    });
    if (responseJson?.status === false) break;
    const rows = Array.isArray(responseJson?.response) ? responseJson.response : [];
    if (!rows.length) break;
    const ownRows = rows.filter((row) => pulseIsRealProfileChatMessage(row, profileExternalId, candidateExternalId));
    if (ownRows.length) {
      return ownRows.reduce((maxTs, row) => Math.max(maxTs, pulseGetRowTimestampMs(row)), 0);
    }
  }
  return 0;
}
async function pulseHasAnyCandidateMessagesInChat(chatUid, authHeader, candidateExternalId, profileExternalId, maxPages = 200) {
  const normalizedChatUid = String(chatUid || '').trim();
  if (!normalizedChatUid) return false;
  for (let page = 1; page <= maxPages; page += 1) {
    const responseJson = await pulseApiJsonViaAlpha('/api/chatList/chatHistory', 'POST', {
      headers: {
        accept: 'application/json, text/plain, */*',
        authorization: authHeader,
        'content-type': 'application/json;charset=UTF-8',
        'x-requested-with': 'XMLHttpRequest',
      },
      body: {
        chat_id: normalizedChatUid,
        page,
      },
      timeoutMs: 20000,
    });
    if (responseJson?.status === false) break;
    const rows = Array.isArray(responseJson?.response) ? responseJson.response : [];
    if (!rows.length) break;
    const hasCandidateMessage = rows.some((row) => pulseIsRealCandidateChatMessage(row, candidateExternalId, profileExternalId));
    if (hasCandidateMessage) return true;
  }
  return false;
}
async function pulseCheckAuthorReplyChatState({ chatUid, authHeader, candidateExternalId, profileExternalId, rawPayload, replyKey, profileName }) {
  const normalizedChatUid = String(chatUid || '').trim();
  if (!normalizedChatUid) return null;
  const candidateAlreadyMessaged = await pulseHasAnyCandidateMessagesInChat(normalizedChatUid, authHeader, candidateExternalId, profileExternalId);
  if (candidateAlreadyMessaged) {
    await pulseAuthorReplyLog('info', '[authorreply] skip candidate already has messages', `${replyKey} profile=${profileName} recipient=${candidateExternalId} chat=${normalizedChatUid}`);
    return { ok: false, skipped: 'candidate-already-has-messages' };
  }
  if (pulseIsAuthorReplyDistance2mEnabled(rawPayload)) {
    const latestOwnMessageTs = await pulseGetLatestProfileMessageTimestampInChat(normalizedChatUid, authHeader, profileExternalId, candidateExternalId, 20);
    if (latestOwnMessageTs && (Date.now() - latestOwnMessageTs) < 2 * 60 * 1000) {
      await pulseAuthorReplyLog('info', '[authorreply] skip candidate by 2min distance', `${replyKey} profile=${profileName} recipient=${candidateExternalId} chat=${normalizedChatUid}`);
      return { ok: false, skipped: 'authorreply-distance-2m' };
    }
  }
  return null;
}
async function pulseSendAuthorReplyWithFallback({ authHeader, payload, profileExternalId, profileName, candidateExternalId, replyKey, candidateName = '' }) {
  const templates = pulsePickTemplatesByKey(payload, String(profileExternalId), replyKey, 3);
  for (let index = 0; index < templates.length; index += 1) {
    const tpl = templates[index];
    const hasText = !!String(tpl?.text || '').trim();
    const hasImage = !!String(tpl?.image || '').trim();
    const order = tpl?.picFirst ? ['image', 'text'] : ['text', 'image'];
    let textOk = false;
    let templateFailed = false;
    for (const part of order) {
      try {
        if (part === 'image' && hasImage) await pulseSendChatImageByExternalIds(profileExternalId, candidateExternalId, tpl.image, authHeader);
        if (part === 'text' && hasText) {
          await pulseSendChatMessageByExternalIds(profileExternalId, candidateExternalId, tpl.text, authHeader);
          textOk = true;
        }
      } catch {
        templateFailed = true;
        break;
      }
    }
    if (!templateFailed && ((hasText && textOk) || (!hasText && hasImage))) {
      return { ok: true, mode: hasText ? 'text' : 'image', tplIndex: index, usedTemplate: tpl };
    }
  }
  await pulseSendChatLikeByExternalIds(profileExternalId, candidateExternalId, profileName || candidateName || 'She', authHeader);
  return { ok: true, mode: 'like', tplIndex: null };
}
async function pulseAuthorReplyHandleSocketEvent(entry) {
  if (!entry || !entry.payload) return { ok: false, skipped: 'no-entry' };
  if (!(await pulseIsAuthorReplyGloballyAllowed())) {
    return { ok: false, skipped: 'bot-stopped' };
  }
  const rawPayload = lastPayload || '';
  if (!pulseIsLikesEnabled(rawPayload)) return { ok: false, skipped: 'likes-disabled' };
  const payloadConfig = pulseParsePayloadString(rawPayload);
  const replyKey = pulseDetectReplyKeyFromSocketEntry(entry);
  if (!replyKey) return { ok: false, skipped: 'unsupported-event' };
  const workProfileId = String(entry.profileId || '').trim();
  const profileExternalId = String(entry.profileExternalId || '').trim();
  const candidateExternalId = String(entry.senderExternalId || '').trim();
  const blockProfileId = workProfileId || profileExternalId;
  const profiles = await pulseFinderReadStorage('profiles') || [];
  const profileInfo = Array.isArray(profiles) ? profiles.find((p) => String(p?.id || '') === workProfileId || String(p?.external_id || '') === profileExternalId) : null;
  const profileName = String(profileInfo?.name || profileInfo?.profile_name || blockProfileId || profileExternalId).trim() || 'She';
  if (!profileExternalId || !candidateExternalId) {
    await pulseAuthorReplyLog('warn', '[authorreply] skip unresolved route', `${replyKey} profile=${workProfileId||'?'} ext=${profileExternalId||'?'} cand=${candidateExternalId||'?'}`);
    return { ok: false, skipped: 'unresolved-route' };
  }
  if (candidateExternalId === profileExternalId) return { ok: false, skipped: 'self-route' };
  const pendingKey = pulseGetBlockKey(blockProfileId, candidateExternalId);
  if (pendingKey && pulseAuthorReplyPending.has(pendingKey)) {
    return { ok: false, skipped: 'authorreply-pending' };
  }
  if (pendingKey) pulseAuthorReplyPending.add(pendingKey);
  try {
    const token = String((await pulseFinderReadStorage('token')) || '').trim();
    if (!token) {
      await pulseAuthorReplyLog('error', '[authorreply] token missing');
      return { ok: false, skipped: 'token-missing' };
    }
    const authHeader = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
    const chatUid = String(entry?.payload?.message_object?.chat_uid || entry?.payload?.chat_uid || '').trim();
    const precheckResult = await pulseCheckAuthorReplyChatState({
      chatUid,
      authHeader,
      candidateExternalId,
      profileExternalId,
      rawPayload,
      replyKey,
      profileName,
    });
    if (precheckResult) return precheckResult;
    if (pulseIsAuthorReplyLikeFirstEnabled(rawPayload)) {
      try {
        await pulseSendChatLikeByExternalIds(profileExternalId, candidateExternalId, profileName || 'She', authHeader);
        await pulseAuthorReplyLog('info', '[authorreply] like-first sent', `${replyKey} profile=${profileName} recipient=${candidateExternalId}`);
      } catch (likeFirstError) {
        await pulseAuthorReplyLog('warn', '[authorreply] like-first skipped', `${replyKey} profile=${profileName} recipient=${candidateExternalId} error=${likeFirstError?.message || String(likeFirstError)}`);
      }
    }
    const delayMs = pulseGetAuthorReplyDelayMs(rawPayload);
    if (delayMs > 0) {
      await pulseSleep(delayMs);
      if (!(await pulseIsAuthorReplyGloballyAllowed())) {
        return { ok: false, skipped: 'bot-stopped' };
      }
      const delayedCheckResult = await pulseCheckAuthorReplyChatState({
        chatUid,
        authHeader,
        candidateExternalId,
        profileExternalId,
        rawPayload,
        replyKey,
        profileName,
      });
      if (delayedCheckResult) return delayedCheckResult;
    }
    const result = await pulseSendAuthorReplyWithFallback({
      authHeader,
      payload: payloadConfig,
      profileExternalId,
      profileName,
      candidateExternalId,
      replyKey,
      candidateName: '',
    });
    if (result?.ok) {
      await pulseAddAuthorReplyBlock(blockProfileId, candidateExternalId, {
        source: 'authorreply',
        replyKey,
        chatUid: String(entry?.payload?.message_object?.chat_uid || entry?.payload?.chat_uid || '').trim(),
      });
      await pulseAuthorReplyLog('info', '[authorreply] sent', `${replyKey} profile=${profileName} recipient=${candidateExternalId} mode=${result.mode}`);
      return { ok: true, sent: true, mode: result.mode, replyKey };
    }
    return { ok: false, skipped: 'send-failed' };
  } finally {
    if (pendingKey) pulseAuthorReplyPending.delete(pendingKey);
  }
}

try {
  globalThis.__PULSE_AUTHORREPLY__ = {
    pulseAuthorReplyHandleSocketEvent,
    pulseSendAuthorReplyWithFallback,
    pulseIsAuthorReplyBlocked,
    pulseAddAuthorReplyBlock,
  };
} catch {}


