background.js 227 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712
  1. // background.js — Service Worker: orchestration, state, tab management, message routing
  2. importScripts(
  3. 'managed-alias-utils.js',
  4. 'background/account-run-history.js',
  5. 'background/panel-bridge.js',
  6. 'background/generated-email-helpers.js',
  7. 'background/signup-flow-helpers.js',
  8. 'background/message-router.js',
  9. 'background/verification-flow.js',
  10. 'background/auto-run-controller.js',
  11. 'background/tab-runtime.js',
  12. 'background/navigation-utils.js',
  13. 'background/logging-status.js',
  14. 'background/steps/registry.js',
  15. 'data/step-definitions.js',
  16. 'background/steps/open-chatgpt.js',
  17. 'background/steps/submit-signup-email.js',
  18. 'background/steps/fill-password.js',
  19. 'background/steps/fetch-signup-code.js',
  20. 'background/steps/fill-profile.js',
  21. 'background/steps/clear-login-cookies.js',
  22. 'background/steps/oauth-login.js',
  23. 'background/steps/fetch-login-code.js',
  24. 'background/steps/confirm-oauth.js',
  25. 'background/steps/platform-verify.js',
  26. 'data/names.js',
  27. 'hotmail-utils.js',
  28. 'microsoft-email.js',
  29. 'luckmail-utils.js',
  30. 'cloudflare-temp-email-utils.js',
  31. 'icloud-utils.js',
  32. 'content/activation-utils.js'
  33. );
  34. const SHARED_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.() || [];
  35. const STEP_IDS = SHARED_STEP_DEFINITIONS
  36. .map((definition) => Number(definition?.id))
  37. .filter(Number.isFinite)
  38. .sort((left, right) => left - right);
  39. const LAST_STEP_ID = STEP_IDS[STEP_IDS.length - 1] || 10;
  40. const FINAL_OAUTH_CHAIN_START_STEP = 7;
  41. const {
  42. extractVerificationCodeFromMessage,
  43. filterHotmailAccountsByUsage,
  44. getLatestHotmailMessage,
  45. getHotmailMailApiRequestConfig,
  46. getHotmailVerificationPollConfig,
  47. getHotmailVerificationRequestTimestamp,
  48. normalizeHotmailServiceMode,
  49. normalizeHotmailMailApiMessages,
  50. pickHotmailAccountForRun,
  51. pickVerificationMessage,
  52. pickVerificationMessageWithFallback,
  53. pickVerificationMessageWithTimeFallback,
  54. shouldClearHotmailCurrentSelection,
  55. } = self.HotmailUtils;
  56. const {
  57. fetchMicrosoftMailboxMessages,
  58. } = self.MultiPageMicrosoftEmail;
  59. const {
  60. DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  61. DEFAULT_LUCKMAIL_BASE_URL,
  62. DEFAULT_LUCKMAIL_EMAIL_TYPE,
  63. buildLuckmailBaselineCursor,
  64. buildLuckmailMailCursor,
  65. filterReusableLuckmailPurchases,
  66. isLuckmailMailNewerThanCursor,
  67. isLuckmailPurchaseReusable,
  68. isLuckmailPurchaseForProject,
  69. isLuckmailPurchasePreserved,
  70. normalizeLuckmailBaseUrl,
  71. normalizeLuckmailEmailType,
  72. normalizeLuckmailMailCursor,
  73. normalizeLuckmailProjectName,
  74. normalizeLuckmailPurchase,
  75. normalizeLuckmailPurchaseId,
  76. normalizeLuckmailPurchaseListPage,
  77. normalizeLuckmailPurchases,
  78. normalizeLuckmailTags,
  79. normalizeLuckmailTokenCode,
  80. normalizeLuckmailTokenMail,
  81. normalizeLuckmailTokenMails,
  82. normalizeLuckmailUsedPurchases,
  83. normalizeTimestamp: normalizeLuckmailTimestamp,
  84. pickLuckmailVerificationMail,
  85. } = self.LuckMailUtils;
  86. const {
  87. DEFAULT_MAIL_PAGE_SIZE: CLOUDFLARE_TEMP_EMAIL_DEFAULT_PAGE_SIZE,
  88. buildCloudflareTempEmailHeaders,
  89. getCloudflareTempEmailAddressFromResponse,
  90. joinCloudflareTempEmailUrl,
  91. normalizeCloudflareTempEmailAddress,
  92. normalizeCloudflareTempEmailBaseUrl,
  93. normalizeCloudflareTempEmailDomain,
  94. normalizeCloudflareTempEmailDomains,
  95. normalizeCloudflareTempEmailMailApiMessages,
  96. } = self.CloudflareTempEmailUtils;
  97. const {
  98. findIcloudAliasByEmail,
  99. getConfiguredIcloudHostPreference,
  100. getIcloudHostHintFromMessage,
  101. getIcloudLoginUrlForHost,
  102. getIcloudMailUrlForHost,
  103. getIcloudSetupUrlForHost,
  104. normalizeBooleanMap,
  105. normalizeIcloudAliasList,
  106. normalizeIcloudHost,
  107. pickReusableIcloudAlias,
  108. toNormalizedEmailSet,
  109. } = self.IcloudUtils;
  110. const {
  111. isRecoverableStep9AuthFailure,
  112. } = self.MultiPageActivationUtils;
  113. const LOG_PREFIX = '[MultiPage:bg]';
  114. const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
  115. const ICLOUD_SETUP_URLS = [
  116. 'https://setup.icloud.com.cn/setup/ws/1',
  117. 'https://setup.icloud.com/setup/ws/1',
  118. ];
  119. const ICLOUD_LOGIN_URLS = [
  120. 'https://www.icloud.com.cn/',
  121. 'https://www.icloud.com/',
  122. ];
  123. const ICLOUD_PROVIDER = 'icloud';
  124. const GMAIL_PROVIDER = 'gmail';
  125. const HOTMAIL_PROVIDER = 'hotmail-api';
  126. const LUCKMAIL_PROVIDER = 'luckmail-api';
  127. const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
  128. const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
  129. const HOTMAIL_MAILBOXES = ['INBOX', 'Junk'];
  130. const STOP_ERROR_MESSAGE = '流程已被用户停止。';
  131. const HUMAN_STEP_DELAY_MIN = 700;
  132. const HUMAN_STEP_DELAY_MAX = 2200;
  133. const STEP6_MAX_ATTEMPTS = 3;
  134. const STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS = 8;
  135. const SUB2API_STEP1_RESPONSE_TIMEOUT_MS = 90000;
  136. const SUB2API_STEP9_RESPONSE_TIMEOUT_MS = 120000;
  137. const DEFAULT_SUB2API_URL = 'https://sub2api.hisence.fun/admin/accounts';
  138. const DEFAULT_SUB2API_GROUP_NAME = 'codex';
  139. const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
  140. const AUTO_RUN_TIMER_ALARM_NAME = 'auto-run-timer';
  141. const AUTO_RUN_TIMER_KIND_SCHEDULED_START = 'scheduled_start';
  142. const AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS = 'between_rounds';
  143. const AUTO_RUN_TIMER_KIND_BEFORE_RETRY = 'before_retry';
  144. const AUTO_RUN_DELAY_MIN_MINUTES = 1;
  145. const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
  146. const AUTO_RUN_RETRY_DELAY_MS = 3000;
  147. const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
  148. const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;
  149. const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
  150. const VERIFICATION_RESEND_COUNT_MIN = 0;
  151. const VERIFICATION_RESEND_COUNT_MAX = 20;
  152. const DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT = 5;
  153. const DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT = 4;
  154. const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
  155. const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
  156. const DEFAULT_CPA_CALLBACK_MODE = 'step9';
  157. const MAIL_2925_MODE_PROVIDE = 'provide';
  158. const MAIL_2925_MODE_RECEIVE = 'receive';
  159. const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
  160. const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
  161. const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
  162. const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
  163. const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
  164. const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = DEFAULT_HOTMAIL_LOCAL_BASE_URL;
  165. const HOTMAIL_LOCAL_HELPER_TIMEOUT_MS = 45000;
  166. const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
  167. const DISPLAY_TIMEZONE = 'Asia/Shanghai';
  168. const MICROSOFT_TOKEN_DNR_RULE_ID = 1001;
  169. const PERSISTENT_ALIAS_STATE_KEYS = ['manualAliasUsage', 'preservedAliases'];
  170. const ACCOUNT_RUN_HISTORY_STORAGE_KEY = 'accountRunHistory';
  171. initializeSessionStorageAccess();
  172. setupDeclarativeNetRequestRules();
  173. function setupDeclarativeNetRequestRules() {
  174. if (!chrome.declarativeNetRequest?.updateDynamicRules) {
  175. return;
  176. }
  177. chrome.declarativeNetRequest.updateDynamicRules({
  178. removeRuleIds: [MICROSOFT_TOKEN_DNR_RULE_ID],
  179. addRules: [{
  180. id: MICROSOFT_TOKEN_DNR_RULE_ID,
  181. priority: 1,
  182. action: {
  183. type: 'modifyHeaders',
  184. requestHeaders: [
  185. { header: 'Origin', operation: 'remove' },
  186. ],
  187. },
  188. condition: {
  189. urlFilter: 'login.microsoftonline.com/*/oauth2/v2.0/token',
  190. resourceTypes: ['xmlhttprequest'],
  191. },
  192. }],
  193. }).catch((error) => {
  194. console.warn(LOG_PREFIX, 'Failed to setup declarativeNetRequest rules:', error?.message || error);
  195. });
  196. }
  197. // ============================================================
  198. // 状态管理(chrome.storage.session + chrome.storage.local)
  199. // ============================================================
  200. const PERSISTED_SETTING_DEFAULTS = {
  201. panelMode: 'cpa',
  202. vpsUrl: '',
  203. vpsPassword: '',
  204. localCpaStep9Mode: DEFAULT_LOCAL_CPA_STEP9_MODE,
  205. cpaCallbackMode: DEFAULT_CPA_CALLBACK_MODE,
  206. sub2apiUrl: DEFAULT_SUB2API_URL,
  207. sub2apiEmail: '',
  208. sub2apiPassword: '',
  209. sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
  210. customPassword: '',
  211. autoRunSkipFailures: false,
  212. autoRunFallbackThreadIntervalMinutes: 0,
  213. autoRunDelayEnabled: false,
  214. autoRunDelayMinutes: 30,
  215. autoStepDelaySeconds: null,
  216. signupVerificationResendCount: DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT,
  217. loginVerificationResendCount: DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT,
  218. mailProvider: '163',
  219. mail2925Mode: DEFAULT_MAIL_2925_MODE,
  220. emailGenerator: 'duck',
  221. autoDeleteUsedIcloudAlias: false,
  222. icloudHostPreference: 'auto',
  223. accountRunHistoryTextEnabled: false,
  224. accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
  225. gmailBaseEmail: '',
  226. mail2925BaseEmail: '',
  227. emailPrefix: '',
  228. inbucketHost: '',
  229. inbucketMailbox: '',
  230. hotmailServiceMode: HOTMAIL_SERVICE_MODE_LOCAL,
  231. hotmailRemoteBaseUrl: DEFAULT_HOTMAIL_REMOTE_BASE_URL,
  232. hotmailLocalBaseUrl: DEFAULT_HOTMAIL_LOCAL_BASE_URL,
  233. cloudflareDomain: '',
  234. cloudflareDomains: [],
  235. cloudflareTempEmailBaseUrl: '',
  236. cloudflareTempEmailAdminAuth: '',
  237. cloudflareTempEmailCustomAuth: '',
  238. cloudflareTempEmailReceiveMailbox: '',
  239. cloudflareTempEmailDomain: '',
  240. cloudflareTempEmailDomains: [],
  241. hotmailAccounts: [],
  242. };
  243. const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
  244. const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
  245. const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
  246. const STEP6_PRE_LOGIN_COOKIE_CLEAR_DELAY_MS = 25000;
  247. const PRE_LOGIN_COOKIE_CLEAR_DOMAINS = [
  248. 'chatgpt.com',
  249. 'chat.openai.com',
  250. 'openai.com',
  251. 'auth.openai.com',
  252. 'auth0.openai.com',
  253. 'accounts.openai.com',
  254. ];
  255. const PRE_LOGIN_COOKIE_CLEAR_ORIGINS = [
  256. 'https://chatgpt.com',
  257. 'https://chat.openai.com',
  258. 'https://auth.openai.com',
  259. 'https://auth0.openai.com',
  260. 'https://accounts.openai.com',
  261. 'https://openai.com',
  262. ];
  263. const DEFAULT_STATE = {
  264. currentStep: 0, // 当前流程执行到的步骤编号。
  265. stepStatuses: Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])),
  266. oauthUrl: null, // 运行时抓取到的 OAuth 地址,不要手动预填。
  267. email: null, // 运行时邮箱,由程序自动获取并写入,不能手动预填。
  268. password: null, // 运行时实际密码,由 customPassword 或程序自动生成后写入。
  269. accounts: [], // 已生成账号记录:{ email, password, createdAt }。
  270. accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。
  271. manualAliasUsage: {},
  272. preservedAliases: {},
  273. lastEmailTimestamp: null, // 最近一次获取到邮箱数据的运行时时间戳。
  274. lastSignupCode: null, // 注册验证码,运行时由程序自动读取并写入。
  275. lastLoginCode: null, // 登录验证码,运行时由程序自动读取并写入。
  276. localhostUrl: null, // 运行时捕获到的 localhost 回调地址,不要手动预填。
  277. sub2apiSessionId: null, // SUB2API OpenAI Auth 会话 ID。
  278. sub2apiOAuthState: null, // SUB2API OpenAI Auth state。
  279. sub2apiGroupId: null, // SUB2API 目标分组 ID。
  280. sub2apiDraftName: null, // SUB2API 本轮预生成的账号名称。
  281. flowStartTime: null, // 当前流程开始时间。
  282. tabRegistry: {}, // 程序维护的标签页注册表。
  283. sourceLastUrls: {}, // 各来源页面最近一次打开的地址记录。
  284. logs: [], // 侧边栏展示的运行日志。
  285. ...PERSISTED_SETTING_DEFAULTS, // 合并 chrome.storage.local 中持久化保存的用户配置。
  286. luckmailApiKey: '',
  287. luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
  288. luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
  289. luckmailDomain: '',
  290. luckmailUsedPurchases: {},
  291. luckmailPreserveTagId: 0,
  292. luckmailPreserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  293. currentLuckmailPurchase: null,
  294. currentLuckmailMailCursor: null,
  295. autoRunning: false, // 当前是否处于自动运行中。
  296. autoRunPhase: 'idle', // 当前自动运行阶段。
  297. autoRunCurrentRun: 0, // 自动运行当前执行到第几轮。
  298. autoRunTotalRuns: 1, // 自动运行计划总轮数。
  299. autoRunAttemptRun: 0, // 当前轮次的重试序号。
  300. autoRunRoundSummaries: [], // 自动运行轮次摘要。
  301. scheduledAutoRunAt: null, // 自动运行计划启动时间戳。
  302. autoRunTimerPlan: null, // 自动运行可恢复计时计划快照。
  303. autoRunCountdownAt: null,
  304. autoRunCountdownTitle: '',
  305. autoRunCountdownNote: '',
  306. signupVerificationRequestedAt: null,
  307. loginVerificationRequestedAt: null,
  308. currentHotmailAccountId: null,
  309. preferredIcloudHost: '',
  310. };
  311. function normalizeAutoRunDelayMinutes(value) {
  312. const numeric = Number(value);
  313. if (!Number.isFinite(numeric)) {
  314. return PERSISTED_SETTING_DEFAULTS.autoRunDelayMinutes;
  315. }
  316. return Math.min(
  317. AUTO_RUN_DELAY_MAX_MINUTES,
  318. Math.max(AUTO_RUN_DELAY_MIN_MINUTES, Math.floor(numeric))
  319. );
  320. }
  321. function normalizeAutoRunFallbackThreadIntervalMinutes(value) {
  322. const rawValue = String(value ?? '').trim();
  323. if (!rawValue) {
  324. return 0;
  325. }
  326. const numeric = Number(rawValue);
  327. if (!Number.isFinite(numeric)) {
  328. return 0;
  329. }
  330. return Math.min(
  331. AUTO_RUN_DELAY_MAX_MINUTES,
  332. Math.max(0, Math.floor(numeric))
  333. );
  334. }
  335. function normalizeAutoStepDelaySeconds(value, fallback = null) {
  336. const rawValue = String(value ?? '').trim();
  337. if (!rawValue) {
  338. return fallback;
  339. }
  340. const numeric = Number(rawValue);
  341. if (!Number.isFinite(numeric)) {
  342. return fallback;
  343. }
  344. return Math.min(
  345. AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS,
  346. Math.max(AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS, Math.floor(numeric))
  347. );
  348. }
  349. function normalizeVerificationResendCount(value, fallback) {
  350. const rawValue = String(value ?? '').trim();
  351. if (!rawValue) {
  352. return fallback;
  353. }
  354. const numeric = Number(rawValue);
  355. if (!Number.isFinite(numeric)) {
  356. return fallback;
  357. }
  358. return Math.min(
  359. VERIFICATION_RESEND_COUNT_MAX,
  360. Math.max(VERIFICATION_RESEND_COUNT_MIN, Math.floor(numeric))
  361. );
  362. }
  363. function resolveLegacyAutoStepDelaySeconds(input = {}) {
  364. const hasLegacyMin = input.autoStepRandomDelayMinSeconds !== undefined;
  365. const hasLegacyMax = input.autoStepRandomDelayMaxSeconds !== undefined;
  366. if (!hasLegacyMin && !hasLegacyMax) {
  367. return undefined;
  368. }
  369. const minSeconds = normalizeAutoStepDelaySeconds(input.autoStepRandomDelayMinSeconds, null);
  370. const maxSeconds = normalizeAutoStepDelaySeconds(input.autoStepRandomDelayMaxSeconds, null);
  371. if (minSeconds === null && maxSeconds === null) {
  372. return null;
  373. }
  374. if (minSeconds === null) {
  375. return maxSeconds;
  376. }
  377. if (maxSeconds === null) {
  378. return minSeconds;
  379. }
  380. return Math.round((minSeconds + maxSeconds) / 2);
  381. }
  382. function normalizeRunCount(value) {
  383. const numeric = Number(value);
  384. if (!Number.isFinite(numeric)) {
  385. return 1;
  386. }
  387. return Math.min(50, Math.max(1, Math.floor(numeric)));
  388. }
  389. function normalizeAutoRunTimerKind(value = '') {
  390. const normalized = String(value || '').trim().toLowerCase();
  391. if (normalized === AUTO_RUN_TIMER_KIND_SCHEDULED_START) {
  392. return AUTO_RUN_TIMER_KIND_SCHEDULED_START;
  393. }
  394. if (normalized === AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS) {
  395. return AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS;
  396. }
  397. if (normalized === AUTO_RUN_TIMER_KIND_BEFORE_RETRY) {
  398. return AUTO_RUN_TIMER_KIND_BEFORE_RETRY;
  399. }
  400. return '';
  401. }
  402. function normalizeAutoRunTimerPlan(plan) {
  403. if (!plan || typeof plan !== 'object' || Array.isArray(plan)) {
  404. return null;
  405. }
  406. const kind = normalizeAutoRunTimerKind(plan.kind);
  407. if (!kind) {
  408. return null;
  409. }
  410. const fireAt = Number(plan.fireAt);
  411. if (!Number.isFinite(fireAt)) {
  412. return null;
  413. }
  414. const totalRuns = normalizeRunCount(plan.totalRuns);
  415. const autoRunSkipFailures = Boolean(plan.autoRunSkipFailures);
  416. const mode = plan.mode === 'continue' ? 'continue' : 'restart';
  417. const currentRun = Math.max(0, Math.min(totalRuns, Math.floor(Number(plan.currentRun) || 0)));
  418. const attemptRun = Math.max(
  419. 0,
  420. Math.min(AUTO_RUN_MAX_RETRIES_PER_ROUND + 1, Math.floor(Number(plan.attemptRun) || 0))
  421. );
  422. const roundSummaries = serializeAutoRunRoundSummaries(totalRuns, plan.roundSummaries);
  423. const countdownTitle = String(plan.countdownTitle || '').trim();
  424. const countdownNote = String(plan.countdownNote || '').trim();
  425. if (kind === AUTO_RUN_TIMER_KIND_SCHEDULED_START) {
  426. return {
  427. kind,
  428. fireAt,
  429. totalRuns,
  430. autoRunSkipFailures,
  431. mode,
  432. currentRun: 0,
  433. attemptRun: 0,
  434. roundSummaries: [],
  435. countdownTitle: countdownTitle || '已计划自动运行',
  436. countdownNote: countdownNote || `计划于 ${formatAutoRunScheduleTime(fireAt)} 开始`,
  437. };
  438. }
  439. if (kind === AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS) {
  440. const normalizedCurrentRun = Math.max(1, Math.min(totalRuns, currentRun));
  441. const normalizedAttemptRun = Math.max(1, attemptRun);
  442. return {
  443. kind,
  444. fireAt,
  445. totalRuns,
  446. autoRunSkipFailures,
  447. mode: 'restart',
  448. currentRun: normalizedCurrentRun,
  449. attemptRun: normalizedAttemptRun,
  450. roundSummaries,
  451. countdownTitle: countdownTitle || '线程间隔中',
  452. countdownNote: countdownNote || `第 ${Math.min(normalizedCurrentRun + 1, totalRuns)}/${totalRuns} 轮即将开始`,
  453. };
  454. }
  455. const normalizedCurrentRun = Math.max(1, Math.min(totalRuns, currentRun));
  456. const normalizedAttemptRun = Math.max(1, attemptRun);
  457. return {
  458. kind,
  459. fireAt,
  460. totalRuns,
  461. autoRunSkipFailures,
  462. mode: 'restart',
  463. currentRun: normalizedCurrentRun,
  464. attemptRun: normalizedAttemptRun,
  465. roundSummaries,
  466. countdownTitle: countdownTitle || '线程间隔中',
  467. countdownNote: countdownNote || `第 ${normalizedCurrentRun}/${totalRuns} 轮第 ${normalizedAttemptRun} 次尝试即将开始`,
  468. };
  469. }
  470. function normalizeAutoRunTimerPlanFromState(state = {}) {
  471. const directPlan = normalizeAutoRunTimerPlan(state.autoRunTimerPlan);
  472. if (directPlan) {
  473. return directPlan;
  474. }
  475. if (state.autoRunPhase !== 'scheduled') {
  476. return null;
  477. }
  478. const legacyScheduledAt = Number(state.scheduledAutoRunAt);
  479. if (!Number.isFinite(legacyScheduledAt)) {
  480. return null;
  481. }
  482. return normalizeAutoRunTimerPlan({
  483. kind: AUTO_RUN_TIMER_KIND_SCHEDULED_START,
  484. fireAt: legacyScheduledAt,
  485. totalRuns: state.scheduledAutoRunPlan?.totalRuns ?? state.autoRunTotalRuns,
  486. autoRunSkipFailures: state.scheduledAutoRunPlan?.autoRunSkipFailures ?? state.autoRunSkipFailures,
  487. mode: state.scheduledAutoRunPlan?.mode,
  488. });
  489. }
  490. function getAutoRunTimerPlanPhase(kind = '') {
  491. return kind === AUTO_RUN_TIMER_KIND_SCHEDULED_START ? 'scheduled' : 'waiting_interval';
  492. }
  493. function getAutoRunTimerStatusPayload(plan) {
  494. const normalizedPlan = normalizeAutoRunTimerPlan(plan);
  495. if (!normalizedPlan) {
  496. return null;
  497. }
  498. const phase = getAutoRunTimerPlanPhase(normalizedPlan.kind);
  499. return {
  500. phase,
  501. currentRun: normalizedPlan.currentRun,
  502. totalRuns: normalizedPlan.totalRuns,
  503. attemptRun: normalizedPlan.attemptRun,
  504. scheduledAt: phase === 'scheduled' ? normalizedPlan.fireAt : null,
  505. countdownAt: normalizedPlan.fireAt,
  506. countdownTitle: normalizedPlan.countdownTitle,
  507. countdownNote: normalizedPlan.countdownNote,
  508. };
  509. }
  510. function normalizeEmailGenerator(value = '') {
  511. const normalized = String(value || '').trim().toLowerCase();
  512. if (normalized === 'custom' || normalized === 'manual') {
  513. return 'custom';
  514. }
  515. if (normalized === 'icloud') {
  516. return 'icloud';
  517. }
  518. if (normalized === 'cloudflare') return 'cloudflare';
  519. if (normalized === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return CLOUDFLARE_TEMP_EMAIL_GENERATOR;
  520. return 'duck';
  521. }
  522. function normalizePanelMode(value = '') {
  523. return String(value || '').trim().toLowerCase() === 'sub2api' ? 'sub2api' : 'cpa';
  524. }
  525. function normalizeMailProvider(value = '') {
  526. const normalized = String(value || '').trim().toLowerCase();
  527. switch (normalized) {
  528. case 'custom':
  529. case ICLOUD_PROVIDER:
  530. case GMAIL_PROVIDER:
  531. case HOTMAIL_PROVIDER:
  532. case LUCKMAIL_PROVIDER:
  533. case CLOUDFLARE_TEMP_EMAIL_PROVIDER:
  534. case '163':
  535. case '163-vip':
  536. case 'qq':
  537. case 'inbucket':
  538. case '2925':
  539. return normalized;
  540. default:
  541. return PERSISTED_SETTING_DEFAULTS.mailProvider;
  542. }
  543. }
  544. function buildLuckmailSessionSettingsPayload(input = {}) {
  545. if (!input || typeof input !== 'object' || Array.isArray(input)) {
  546. return {};
  547. }
  548. const payload = {};
  549. if (input.luckmailApiKey !== undefined) {
  550. payload.luckmailApiKey = String(input.luckmailApiKey || '');
  551. }
  552. if (input.luckmailBaseUrl !== undefined) {
  553. payload.luckmailBaseUrl = normalizeLuckmailBaseUrl(input.luckmailBaseUrl);
  554. }
  555. if (input.luckmailEmailType !== undefined) {
  556. payload.luckmailEmailType = normalizeLuckmailEmailType(input.luckmailEmailType);
  557. }
  558. if (input.luckmailDomain !== undefined) {
  559. payload.luckmailDomain = String(input.luckmailDomain || '').trim();
  560. }
  561. if (input.luckmailUsedPurchases !== undefined) {
  562. payload.luckmailUsedPurchases = normalizeLuckmailUsedPurchases(input.luckmailUsedPurchases);
  563. }
  564. if (input.luckmailPreserveTagId !== undefined) {
  565. payload.luckmailPreserveTagId = Number(input.luckmailPreserveTagId) || 0;
  566. }
  567. if (input.luckmailPreserveTagName !== undefined) {
  568. payload.luckmailPreserveTagName = String(input.luckmailPreserveTagName || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME;
  569. }
  570. if (input.currentLuckmailPurchase !== undefined) {
  571. payload.currentLuckmailPurchase = input.currentLuckmailPurchase
  572. ? normalizeLuckmailPurchase(input.currentLuckmailPurchase)
  573. : null;
  574. }
  575. if (input.currentLuckmailMailCursor !== undefined) {
  576. payload.currentLuckmailMailCursor = input.currentLuckmailMailCursor
  577. ? normalizeLuckmailMailCursor(input.currentLuckmailMailCursor)
  578. : null;
  579. }
  580. return payload;
  581. }
  582. function normalizeMail2925Mode(value = '') {
  583. return String(value || '').trim().toLowerCase() === MAIL_2925_MODE_RECEIVE
  584. ? MAIL_2925_MODE_RECEIVE
  585. : DEFAULT_MAIL_2925_MODE;
  586. }
  587. function normalizeLocalCpaStep9Mode(value = '') {
  588. return String(value || '').trim().toLowerCase() === 'bypass'
  589. ? 'bypass'
  590. : DEFAULT_LOCAL_CPA_STEP9_MODE;
  591. }
  592. function normalizeCpaCallbackMode(value = '') {
  593. const normalized = String(value || '').trim().toLowerCase();
  594. if (normalized === 'step7' || normalized === 'step6') {
  595. return 'step7';
  596. }
  597. if (normalized === 'step9' || normalized === 'step8') {
  598. return 'step9';
  599. }
  600. return DEFAULT_CPA_CALLBACK_MODE;
  601. }
  602. function normalizeCloudflareDomain(rawValue = '') {
  603. let value = String(rawValue || '').trim().toLowerCase();
  604. if (!value) return '';
  605. value = value.replace(/^@+/, '');
  606. value = value.replace(/^https?:\/\//, '');
  607. value = value.replace(/\/.*$/, '');
  608. if (!/^[a-z0-9.-]+\.[a-z]{2,}$/.test(value)) return '';
  609. return value;
  610. }
  611. function normalizeCloudflareDomains(values) {
  612. const normalizedDomains = [];
  613. const seen = new Set();
  614. for (const value of Array.isArray(values) ? values : []) {
  615. const normalized = normalizeCloudflareDomain(value);
  616. if (!normalized || seen.has(normalized)) continue;
  617. seen.add(normalized);
  618. normalizedDomains.push(normalized);
  619. }
  620. return normalizedDomains;
  621. }
  622. function normalizeHotmailRemoteBaseUrl(rawValue = '') {
  623. const value = String(rawValue || '').trim();
  624. if (!value) return DEFAULT_HOTMAIL_REMOTE_BASE_URL;
  625. try {
  626. const parsed = new URL(value);
  627. if (!['http:', 'https:'].includes(parsed.protocol)) {
  628. return DEFAULT_HOTMAIL_REMOTE_BASE_URL;
  629. }
  630. if (parsed.pathname.endsWith('/api/mail-new') || parsed.pathname.endsWith('/api/mail-all') || parsed.pathname === '/api.html') {
  631. parsed.pathname = '';
  632. parsed.search = '';
  633. parsed.hash = '';
  634. }
  635. return parsed.toString().replace(/\/$/, '');
  636. } catch {
  637. return DEFAULT_HOTMAIL_REMOTE_BASE_URL;
  638. }
  639. }
  640. function normalizeHotmailLocalBaseUrl(rawValue = '') {
  641. const value = String(rawValue || '').trim();
  642. if (!value) return DEFAULT_HOTMAIL_LOCAL_BASE_URL;
  643. try {
  644. const parsed = new URL(value);
  645. if (!['http:', 'https:'].includes(parsed.protocol)) {
  646. return DEFAULT_HOTMAIL_LOCAL_BASE_URL;
  647. }
  648. if (['/messages', '/code', '/clear', '/token'].includes(parsed.pathname)) {
  649. parsed.pathname = '';
  650. parsed.search = '';
  651. parsed.hash = '';
  652. }
  653. return parsed.toString().replace(/\/$/, '');
  654. } catch {
  655. return DEFAULT_HOTMAIL_LOCAL_BASE_URL;
  656. }
  657. }
  658. function normalizeAccountRunHistoryHelperBaseUrl(rawValue = '') {
  659. const value = String(rawValue || '').trim();
  660. if (!value) return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL;
  661. try {
  662. const parsed = new URL(value);
  663. if (parsed.pathname === '/append-account-log') {
  664. parsed.pathname = '';
  665. parsed.search = '';
  666. parsed.hash = '';
  667. }
  668. return normalizeHotmailLocalBaseUrl(parsed.toString());
  669. } catch {
  670. return normalizeHotmailLocalBaseUrl(value);
  671. }
  672. }
  673. function getHotmailServiceSettings(state = {}) {
  674. return {
  675. mode: normalizeHotmailServiceMode(state.hotmailServiceMode),
  676. remoteBaseUrl: normalizeHotmailRemoteBaseUrl(state.hotmailRemoteBaseUrl),
  677. localBaseUrl: normalizeHotmailLocalBaseUrl(state.hotmailLocalBaseUrl),
  678. };
  679. }
  680. function getCloudflareTempEmailConfig(state = {}) {
  681. return {
  682. baseUrl: normalizeCloudflareTempEmailBaseUrl(state.cloudflareTempEmailBaseUrl),
  683. adminAuth: String(state.cloudflareTempEmailAdminAuth || ''),
  684. customAuth: String(state.cloudflareTempEmailCustomAuth || ''),
  685. receiveMailbox: normalizeCloudflareTempEmailReceiveMailbox(state.cloudflareTempEmailReceiveMailbox),
  686. domain: normalizeCloudflareTempEmailDomain(state.cloudflareTempEmailDomain),
  687. domains: normalizeCloudflareTempEmailDomains(state.cloudflareTempEmailDomains),
  688. };
  689. }
  690. function normalizeCloudflareTempEmailReceiveMailbox(value = '') {
  691. const normalized = normalizeCloudflareTempEmailAddress(value);
  692. if (!normalized) return '';
  693. return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized) ? normalized : '';
  694. }
  695. function resolveCloudflareTempEmailPollTargetEmail(state = {}, pollPayload = {}, config = getCloudflareTempEmailConfig(state)) {
  696. const configuredReceiveMailbox = normalizeCloudflareTempEmailReceiveMailbox(config.receiveMailbox);
  697. if (configuredReceiveMailbox) {
  698. return configuredReceiveMailbox;
  699. }
  700. const requestedTarget = normalizeCloudflareTempEmailReceiveMailbox(pollPayload.targetEmail);
  701. if (requestedTarget) {
  702. return requestedTarget;
  703. }
  704. return normalizeCloudflareTempEmailReceiveMailbox(state.email);
  705. }
  706. function normalizePersistentSettingValue(key, value) {
  707. switch (key) {
  708. case 'panelMode':
  709. return normalizePanelMode(value);
  710. case 'vpsUrl':
  711. return String(value || '').trim();
  712. case 'vpsPassword':
  713. return String(value || '');
  714. case 'localCpaStep9Mode':
  715. return normalizeLocalCpaStep9Mode(value);
  716. case 'cpaCallbackMode':
  717. return normalizeCpaCallbackMode(value);
  718. case 'sub2apiUrl':
  719. return String(value || '').trim();
  720. case 'sub2apiEmail':
  721. return String(value || '').trim();
  722. case 'sub2apiPassword':
  723. return String(value || '');
  724. case 'sub2apiGroupName':
  725. return String(value || '').trim();
  726. case 'customPassword':
  727. return String(value || '');
  728. case 'autoRunSkipFailures':
  729. case 'autoRunDelayEnabled':
  730. return Boolean(value);
  731. case 'autoRunFallbackThreadIntervalMinutes':
  732. return normalizeAutoRunFallbackThreadIntervalMinutes(value);
  733. case 'autoRunDelayMinutes':
  734. return normalizeAutoRunDelayMinutes(value);
  735. case 'autoStepDelaySeconds':
  736. return normalizeAutoStepDelaySeconds(value, PERSISTED_SETTING_DEFAULTS.autoStepDelaySeconds);
  737. case 'signupVerificationResendCount':
  738. return normalizeVerificationResendCount(value, DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT);
  739. case 'loginVerificationResendCount':
  740. return normalizeVerificationResendCount(value, DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT);
  741. case 'mailProvider':
  742. return normalizeMailProvider(value);
  743. case 'mail2925Mode':
  744. return normalizeMail2925Mode(value);
  745. case 'emailGenerator':
  746. return normalizeEmailGenerator(value);
  747. case 'autoDeleteUsedIcloudAlias':
  748. case 'accountRunHistoryTextEnabled':
  749. return Boolean(value);
  750. case 'icloudHostPreference':
  751. return normalizeIcloudHost(value) || 'auto';
  752. case 'accountRunHistoryHelperBaseUrl':
  753. return normalizeAccountRunHistoryHelperBaseUrl(value);
  754. case 'gmailBaseEmail':
  755. case 'mail2925BaseEmail':
  756. case 'emailPrefix':
  757. return String(value || '').trim();
  758. case 'inbucketHost':
  759. return String(value || '').trim();
  760. case 'inbucketMailbox':
  761. return String(value || '').trim();
  762. case 'hotmailServiceMode':
  763. return normalizeHotmailServiceMode(value);
  764. case 'hotmailRemoteBaseUrl':
  765. return normalizeHotmailRemoteBaseUrl(value);
  766. case 'hotmailLocalBaseUrl':
  767. return normalizeHotmailLocalBaseUrl(value);
  768. case 'cloudflareDomain':
  769. return normalizeCloudflareDomain(value);
  770. case 'cloudflareDomains':
  771. return normalizeCloudflareDomains(value);
  772. case 'cloudflareTempEmailBaseUrl':
  773. return normalizeCloudflareTempEmailBaseUrl(value);
  774. case 'cloudflareTempEmailAdminAuth':
  775. case 'cloudflareTempEmailCustomAuth':
  776. return String(value || '');
  777. case 'cloudflareTempEmailReceiveMailbox':
  778. return normalizeCloudflareTempEmailReceiveMailbox(value);
  779. case 'cloudflareTempEmailDomain':
  780. return normalizeCloudflareTempEmailDomain(value);
  781. case 'cloudflareTempEmailDomains':
  782. return normalizeCloudflareTempEmailDomains(value);
  783. case 'hotmailAccounts':
  784. return normalizeHotmailAccounts(value);
  785. default:
  786. return value;
  787. }
  788. }
  789. function buildPersistentSettingsPayload(input = {}, options = {}) {
  790. const { fillDefaults = false, requireKnownKeys = false } = options;
  791. if (!input || typeof input !== 'object' || Array.isArray(input)) {
  792. throw new Error('\u914d\u7f6e\u5185\u5bb9\u683c\u5f0f\u65e0\u6548\u3002');
  793. }
  794. const normalizedInput = { ...input };
  795. if (normalizedInput.autoStepDelaySeconds === undefined) {
  796. const legacyAutoStepDelaySeconds = resolveLegacyAutoStepDelaySeconds(normalizedInput);
  797. if (legacyAutoStepDelaySeconds !== undefined) {
  798. normalizedInput.autoStepDelaySeconds = legacyAutoStepDelaySeconds;
  799. }
  800. }
  801. const payload = {};
  802. let matchedKeyCount = 0;
  803. for (const key of PERSISTED_SETTING_KEYS) {
  804. if (normalizedInput[key] !== undefined) {
  805. payload[key] = normalizePersistentSettingValue(key, normalizedInput[key]);
  806. matchedKeyCount += 1;
  807. } else if (fillDefaults) {
  808. payload[key] = normalizePersistentSettingValue(key, PERSISTED_SETTING_DEFAULTS[key]);
  809. }
  810. }
  811. if (requireKnownKeys && matchedKeyCount === 0) {
  812. throw new Error('\u914d\u7f6e\u6587\u4ef6\u4e2d\u6ca1\u6709\u53ef\u8bc6\u522b\u7684\u914d\u7f6e\u5185\u5bb9\u3002');
  813. }
  814. if (payload.cloudflareDomains) {
  815. const domains = normalizeCloudflareDomains(payload.cloudflareDomains);
  816. if (payload.cloudflareDomain && !domains.includes(payload.cloudflareDomain)) {
  817. domains.unshift(payload.cloudflareDomain);
  818. }
  819. payload.cloudflareDomains = domains;
  820. }
  821. if (payload.cloudflareTempEmailDomains) {
  822. const domains = normalizeCloudflareTempEmailDomains(payload.cloudflareTempEmailDomains);
  823. if (payload.cloudflareTempEmailDomain && !domains.includes(payload.cloudflareTempEmailDomain)) {
  824. domains.unshift(payload.cloudflareTempEmailDomain);
  825. }
  826. payload.cloudflareTempEmailDomains = domains;
  827. }
  828. return payload;
  829. }
  830. async function getPersistedSettings() {
  831. const stored = await chrome.storage.local.get([...PERSISTED_SETTING_KEYS, ...LEGACY_AUTO_STEP_DELAY_KEYS]);
  832. return buildPersistentSettingsPayload(stored, { fillDefaults: true });
  833. }
  834. async function getPersistedAliasState() {
  835. try {
  836. const stored = await chrome.storage.local.get(PERSISTENT_ALIAS_STATE_KEYS);
  837. return {
  838. manualAliasUsage: normalizeBooleanMap(stored.manualAliasUsage),
  839. preservedAliases: normalizeBooleanMap(stored.preservedAliases),
  840. };
  841. } catch (err) {
  842. console.warn(LOG_PREFIX, 'Failed to read persisted iCloud alias state:', err?.message || err);
  843. return {
  844. manualAliasUsage: {},
  845. preservedAliases: {},
  846. };
  847. }
  848. }
  849. async function getState() {
  850. const [state, persistedSettings, persistedAliasState, accountRunHistory] = await Promise.all([
  851. chrome.storage.session.get(null),
  852. getPersistedSettings(),
  853. getPersistedAliasState(),
  854. accountRunHistoryHelpers?.getPersistedAccountRunHistory?.() || [],
  855. ]);
  856. return { ...DEFAULT_STATE, ...persistedSettings, ...persistedAliasState, accountRunHistory, ...state };
  857. }
  858. async function initializeSessionStorageAccess() {
  859. try {
  860. if (chrome.storage?.session?.setAccessLevel) {
  861. await chrome.storage.session.setAccessLevel({
  862. accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS',
  863. });
  864. console.log(LOG_PREFIX, 'Enabled storage.session for content scripts');
  865. }
  866. } catch (err) {
  867. console.warn(LOG_PREFIX, 'Failed to enable storage.session for content scripts:', err?.message || err);
  868. }
  869. }
  870. async function setState(updates) {
  871. console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200));
  872. if (Object.keys(updates || {}).length > 0) {
  873. await chrome.storage.session.set(updates);
  874. const persistentAliasUpdates = {};
  875. if (Object.prototype.hasOwnProperty.call(updates, 'manualAliasUsage')) {
  876. persistentAliasUpdates.manualAliasUsage = normalizeBooleanMap(updates.manualAliasUsage);
  877. }
  878. if (Object.prototype.hasOwnProperty.call(updates, 'preservedAliases')) {
  879. persistentAliasUpdates.preservedAliases = normalizeBooleanMap(updates.preservedAliases);
  880. }
  881. if (Object.keys(persistentAliasUpdates).length > 0) {
  882. await chrome.storage.local.set(persistentAliasUpdates);
  883. }
  884. }
  885. }
  886. async function setPersistentSettings(updates) {
  887. const persistedUpdates = buildPersistentSettingsPayload(updates);
  888. if (Object.keys(persistedUpdates).length > 0) {
  889. await chrome.storage.local.set(persistedUpdates);
  890. }
  891. }
  892. function buildSettingsExportFilename(date = new Date()) {
  893. const pad = (value) => String(value).padStart(2, '0');
  894. return `${SETTINGS_EXPORT_FILENAME_PREFIX}-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}.json`;
  895. }
  896. async function exportSettingsBundle() {
  897. const settings = await getPersistedSettings();
  898. const bundle = {
  899. schemaVersion: SETTINGS_EXPORT_SCHEMA_VERSION,
  900. exportedAt: new Date().toISOString(),
  901. extensionVersion: chrome.runtime.getManifest().version,
  902. settings,
  903. };
  904. return {
  905. fileName: buildSettingsExportFilename(),
  906. fileContent: JSON.stringify(bundle, null, 2),
  907. };
  908. }
  909. async function importSettingsBundle(configBundle) {
  910. const state = await ensureManualInteractionAllowed('\u5bfc\u5165\u914d\u7f6e');
  911. if (Object.values(state.stepStatuses || {}).some((status) => status === 'running')) {
  912. throw new Error('\u5f53\u524d\u6709\u6b65\u9aa4\u6b63\u5728\u6267\u884c\uff0c\u65e0\u6cd5\u5bfc\u5165\u914d\u7f6e\u3002');
  913. }
  914. if (!configBundle || typeof configBundle !== 'object' || Array.isArray(configBundle)) {
  915. throw new Error('\u914d\u7f6e\u6587\u4ef6\u5185\u5bb9\u65e0\u6548\u3002');
  916. }
  917. const schemaVersion = Number(configBundle.schemaVersion);
  918. if (schemaVersion !== SETTINGS_EXPORT_SCHEMA_VERSION) {
  919. throw new Error(`\u4ec5\u652f\u6301\u5bfc\u5165 schemaVersion=${SETTINGS_EXPORT_SCHEMA_VERSION} \u7684\u914d\u7f6e\u6587\u4ef6\u3002`);
  920. }
  921. if (!configBundle.settings || typeof configBundle.settings !== 'object' || Array.isArray(configBundle.settings)) {
  922. throw new Error('\u914d\u7f6e\u6587\u4ef6\u7f3a\u5c11 settings \u914d\u7f6e\u6bb5\u3002');
  923. }
  924. const importedSettings = buildPersistentSettingsPayload(configBundle.settings, {
  925. fillDefaults: true,
  926. requireKnownKeys: true,
  927. });
  928. await setPersistentSettings(importedSettings);
  929. const sessionUpdates = {
  930. ...importedSettings,
  931. currentHotmailAccountId: null,
  932. email: null,
  933. };
  934. await setState(sessionUpdates);
  935. broadcastDataUpdate({
  936. ...importedSettings,
  937. currentHotmailAccountId: null,
  938. ...(sessionUpdates.email !== undefined ? { email: sessionUpdates.email } : {}),
  939. });
  940. return getState();
  941. }
  942. function broadcastDataUpdate(payload) {
  943. chrome.runtime.sendMessage({
  944. type: 'DATA_UPDATED',
  945. payload,
  946. }).catch(() => { });
  947. }
  948. function broadcastIcloudAliasesChanged(payload = {}) {
  949. chrome.runtime.sendMessage({
  950. type: 'ICLOUD_ALIASES_CHANGED',
  951. payload,
  952. }).catch(() => { });
  953. }
  954. async function setEmailStateSilently(email) {
  955. await setState({ email });
  956. broadcastDataUpdate({ email });
  957. }
  958. async function setEmailState(email) {
  959. await setEmailStateSilently(email);
  960. if (email) {
  961. await resumeAutoRunIfWaitingForEmail();
  962. }
  963. }
  964. async function setPasswordState(password) {
  965. await setState({ password });
  966. broadcastDataUpdate({ password });
  967. }
  968. function getLuckmailUsedPurchases(state = {}) {
  969. return normalizeLuckmailUsedPurchases(state?.luckmailUsedPurchases);
  970. }
  971. function getLuckmailPreserveTagInfo(state = {}) {
  972. return {
  973. id: Number(state?.luckmailPreserveTagId) || 0,
  974. name: String(state?.luckmailPreserveTagName || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  975. };
  976. }
  977. async function setLuckmailUsedPurchasesState(usedPurchases) {
  978. const normalizedUsedPurchases = normalizeLuckmailUsedPurchases(usedPurchases);
  979. await setState({ luckmailUsedPurchases: normalizedUsedPurchases });
  980. broadcastDataUpdate({ luckmailUsedPurchases: normalizedUsedPurchases });
  981. return normalizedUsedPurchases;
  982. }
  983. async function setLuckmailPurchaseUsedState(purchaseId, used) {
  984. const normalizedPurchaseId = normalizeLuckmailPurchaseId(purchaseId);
  985. if (!normalizedPurchaseId) {
  986. throw new Error('LuckMail 邮箱 ID 无效。');
  987. }
  988. const state = await getState();
  989. const usedPurchases = getLuckmailUsedPurchases(state);
  990. if (used) {
  991. usedPurchases[normalizedPurchaseId] = true;
  992. } else {
  993. delete usedPurchases[normalizedPurchaseId];
  994. }
  995. await setLuckmailUsedPurchasesState(usedPurchases);
  996. return {
  997. purchaseId: Number(normalizedPurchaseId),
  998. used: Boolean(used),
  999. };
  1000. }
  1001. async function setLuckmailPreserveTagInfo(tag) {
  1002. const normalizedTags = normalizeLuckmailTags([tag]);
  1003. const normalizedTag = normalizedTags[0] || {
  1004. id: 0,
  1005. name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  1006. };
  1007. const updates = {
  1008. luckmailPreserveTagId: Number(normalizedTag.id) || 0,
  1009. luckmailPreserveTagName: String(normalizedTag.name || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  1010. };
  1011. await setState(updates);
  1012. broadcastDataUpdate(updates);
  1013. return updates;
  1014. }
  1015. async function setLuckmailPurchaseState(purchase) {
  1016. const normalizedPurchase = purchase ? normalizeLuckmailPurchase(purchase) : null;
  1017. await setState({ currentLuckmailPurchase: normalizedPurchase });
  1018. broadcastDataUpdate({ currentLuckmailPurchase: normalizedPurchase });
  1019. return normalizedPurchase;
  1020. }
  1021. async function setLuckmailMailCursorState(cursor) {
  1022. const normalizedCursor = cursor ? normalizeLuckmailMailCursor(cursor) : null;
  1023. await setState({ currentLuckmailMailCursor: normalizedCursor });
  1024. return normalizedCursor;
  1025. }
  1026. async function clearLuckmailRuntimeState(options = {}) {
  1027. const { clearEmail = false } = options;
  1028. const updates = {
  1029. currentLuckmailPurchase: null,
  1030. currentLuckmailMailCursor: null,
  1031. };
  1032. if (clearEmail) {
  1033. updates.email = null;
  1034. }
  1035. await setState(updates);
  1036. broadcastDataUpdate(updates);
  1037. }
  1038. function getManualAliasUsageMap(state) {
  1039. return normalizeBooleanMap(state?.manualAliasUsage);
  1040. }
  1041. function getPreservedAliasMap(state) {
  1042. return normalizeBooleanMap(state?.preservedAliases);
  1043. }
  1044. function isAliasPreserved(state, email) {
  1045. const normalizedEmail = String(email || '').trim().toLowerCase();
  1046. if (!normalizedEmail) return false;
  1047. return Boolean(getPreservedAliasMap(state)[normalizedEmail]);
  1048. }
  1049. function getEffectiveUsedEmails(state) {
  1050. return toNormalizedEmailSet(getManualAliasUsageMap(state));
  1051. }
  1052. async function setIcloudAliasUsedState(payload = {}, options = {}) {
  1053. const email = String(payload.email || '').trim().toLowerCase();
  1054. if (!email) {
  1055. throw new Error('未提供 iCloud 隐私邮箱地址。');
  1056. }
  1057. const used = Boolean(payload.used);
  1058. const state = await getState();
  1059. const manualAliasUsage = getManualAliasUsageMap(state);
  1060. manualAliasUsage[email] = used;
  1061. await setState({ manualAliasUsage });
  1062. if (!options.silentLog) {
  1063. await addLog(`iCloud:已将 ${email} 标记为${used ? '已用' : '未用'}`, 'ok');
  1064. }
  1065. broadcastIcloudAliasesChanged({ reason: 'used-updated', email, used });
  1066. return { email, used };
  1067. }
  1068. async function setIcloudAliasPreservedState(payload = {}) {
  1069. const email = String(payload.email || '').trim().toLowerCase();
  1070. if (!email) {
  1071. throw new Error('未提供 iCloud 隐私邮箱地址。');
  1072. }
  1073. const preserved = Boolean(payload.preserved);
  1074. const state = await getState();
  1075. const preservedAliases = getPreservedAliasMap(state);
  1076. preservedAliases[email] = preserved;
  1077. await setState({ preservedAliases });
  1078. await addLog(`iCloud:已将 ${email} ${preserved ? '设为保留' : '取消保留'}`, 'ok');
  1079. broadcastIcloudAliasesChanged({ reason: 'preserved-updated', email, preserved });
  1080. return { email, preserved };
  1081. }
  1082. async function resetState() {
  1083. console.log(LOG_PREFIX, 'Resetting all state');
  1084. // Preserve settings and persistent data across resets
  1085. const [prev, persistedSettings, persistedAliasState] = await Promise.all([
  1086. chrome.storage.session.get([
  1087. 'seenCodes',
  1088. 'seenInbucketMailIds',
  1089. 'accounts',
  1090. 'tabRegistry',
  1091. 'sourceLastUrls',
  1092. 'luckmailApiKey',
  1093. 'luckmailBaseUrl',
  1094. 'luckmailEmailType',
  1095. 'luckmailDomain',
  1096. 'luckmailUsedPurchases',
  1097. 'luckmailPreserveTagId',
  1098. 'luckmailPreserveTagName',
  1099. 'preferredIcloudHost',
  1100. ]),
  1101. getPersistedSettings(),
  1102. getPersistedAliasState(),
  1103. ]);
  1104. await chrome.storage.session.clear();
  1105. await chrome.storage.session.set({
  1106. ...DEFAULT_STATE,
  1107. ...persistedSettings,
  1108. ...persistedAliasState,
  1109. seenCodes: prev.seenCodes || [],
  1110. seenInbucketMailIds: prev.seenInbucketMailIds || [],
  1111. accounts: prev.accounts || [],
  1112. tabRegistry: prev.tabRegistry || {},
  1113. sourceLastUrls: prev.sourceLastUrls || {},
  1114. luckmailApiKey: String(prev.luckmailApiKey || ''),
  1115. luckmailBaseUrl: normalizeLuckmailBaseUrl(prev.luckmailBaseUrl),
  1116. luckmailEmailType: normalizeLuckmailEmailType(prev.luckmailEmailType),
  1117. luckmailDomain: String(prev.luckmailDomain || '').trim(),
  1118. luckmailUsedPurchases: normalizeLuckmailUsedPurchases(prev.luckmailUsedPurchases),
  1119. luckmailPreserveTagId: Number(prev.luckmailPreserveTagId) || 0,
  1120. luckmailPreserveTagName: String(prev.luckmailPreserveTagName || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  1121. currentLuckmailPurchase: null,
  1122. currentLuckmailMailCursor: null,
  1123. preferredIcloudHost: prev.preferredIcloudHost || '',
  1124. });
  1125. }
  1126. /**
  1127. * Generate a random password: 14 chars, mix of uppercase, lowercase, digits, symbols.
  1128. */
  1129. function generatePassword() {
  1130. const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
  1131. const lower = 'abcdefghjkmnpqrstuvwxyz';
  1132. const digits = '23456789';
  1133. const symbols = '!@#$%&*?';
  1134. const all = upper + lower + digits + symbols;
  1135. // Ensure at least one of each type
  1136. let pw = '';
  1137. pw += upper[Math.floor(Math.random() * upper.length)];
  1138. pw += lower[Math.floor(Math.random() * lower.length)];
  1139. pw += digits[Math.floor(Math.random() * digits.length)];
  1140. pw += symbols[Math.floor(Math.random() * symbols.length)];
  1141. // Fill remaining 10 chars
  1142. for (let i = 0; i < 10; i++) {
  1143. pw += all[Math.floor(Math.random() * all.length)];
  1144. }
  1145. // Shuffle
  1146. return pw.split('').sort(() => Math.random() - 0.5).join('');
  1147. }
  1148. function normalizeHotmailAccount(account = {}) {
  1149. const normalizedLastAuthAt = Number.isFinite(Number(account.lastAuthAt)) ? Number(account.lastAuthAt) : 0;
  1150. const normalizedStatus = String(
  1151. account.status
  1152. || (normalizedLastAuthAt > 0 ? 'authorized' : 'pending')
  1153. );
  1154. return {
  1155. id: String(account.id || crypto.randomUUID()),
  1156. email: String(account.email || '').trim(),
  1157. password: String(account.password || ''),
  1158. clientId: String(account.clientId || '').trim(),
  1159. refreshToken: String(account.refreshToken || ''),
  1160. status: normalizedStatus,
  1161. enabled: account.enabled !== undefined ? Boolean(account.enabled) : true,
  1162. used: Boolean(account.used),
  1163. lastUsedAt: Number.isFinite(Number(account.lastUsedAt)) ? Number(account.lastUsedAt) : 0,
  1164. lastAuthAt: normalizedLastAuthAt,
  1165. lastError: String(account.lastError || ''),
  1166. };
  1167. }
  1168. function normalizeHotmailAccounts(accounts) {
  1169. if (!Array.isArray(accounts)) return [];
  1170. const deduped = new Map();
  1171. for (const account of accounts) {
  1172. const normalized = normalizeHotmailAccount(account);
  1173. if (!normalized.email && !normalized.id) continue;
  1174. deduped.set(normalized.id, normalized);
  1175. }
  1176. return [...deduped.values()];
  1177. }
  1178. function findHotmailAccount(accounts, accountId) {
  1179. return normalizeHotmailAccounts(accounts).find((account) => account.id === accountId) || null;
  1180. }
  1181. function isHotmailProvider(stateOrProvider) {
  1182. const provider = typeof stateOrProvider === 'string'
  1183. ? stateOrProvider
  1184. : stateOrProvider?.mailProvider;
  1185. return provider === HOTMAIL_PROVIDER;
  1186. }
  1187. function isLuckmailProvider(stateOrProvider) {
  1188. const provider = typeof stateOrProvider === 'string'
  1189. ? stateOrProvider
  1190. : stateOrProvider?.mailProvider;
  1191. return provider === LUCKMAIL_PROVIDER;
  1192. }
  1193. function isCustomMailProvider(stateOrProvider) {
  1194. const provider = typeof stateOrProvider === 'string'
  1195. ? stateOrProvider
  1196. : stateOrProvider?.mailProvider;
  1197. return provider === 'custom';
  1198. }
  1199. function getMail2925Mode(stateOrMode) {
  1200. if (typeof stateOrMode === 'string') {
  1201. return normalizeMail2925Mode(stateOrMode);
  1202. }
  1203. return normalizeMail2925Mode(stateOrMode?.mail2925Mode);
  1204. }
  1205. async function syncHotmailAccounts(accounts) {
  1206. const normalized = normalizeHotmailAccounts(accounts);
  1207. await setPersistentSettings({ hotmailAccounts: normalized });
  1208. await setState({ hotmailAccounts: normalized });
  1209. broadcastDataUpdate({ hotmailAccounts: normalized });
  1210. return normalized;
  1211. }
  1212. async function upsertHotmailAccount(input) {
  1213. const state = await getState();
  1214. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  1215. const normalizedEmail = String(input?.email || '').trim().toLowerCase();
  1216. const existing = input?.id
  1217. ? findHotmailAccount(accounts, input.id)
  1218. : accounts.find((account) => account.email.toLowerCase() === normalizedEmail) || null;
  1219. const credentialsChanged = !existing
  1220. || (input?.clientId !== undefined && String(input.clientId).trim() !== existing.clientId)
  1221. || (input?.refreshToken !== undefined && String(input.refreshToken).trim() !== existing.refreshToken)
  1222. || (input?.email !== undefined && String(input.email).trim().toLowerCase() !== existing.email.toLowerCase());
  1223. const normalized = normalizeHotmailAccount({
  1224. ...(existing || {}),
  1225. ...(credentialsChanged ? {
  1226. status: 'pending',
  1227. lastAuthAt: 0,
  1228. lastError: '',
  1229. } : {}),
  1230. ...input,
  1231. id: input?.id || existing?.id || crypto.randomUUID(),
  1232. });
  1233. const nextAccounts = existing
  1234. ? accounts.map((account) => (account.id === normalized.id ? normalized : account))
  1235. : [...accounts, normalized];
  1236. await syncHotmailAccounts(nextAccounts);
  1237. return normalized;
  1238. }
  1239. async function deleteHotmailAccount(accountId) {
  1240. const state = await getState();
  1241. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  1242. const nextAccounts = accounts.filter((account) => account.id !== accountId);
  1243. await syncHotmailAccounts(nextAccounts);
  1244. if (state.currentHotmailAccountId === accountId) {
  1245. await setState({ currentHotmailAccountId: null });
  1246. if (isHotmailProvider(state)) {
  1247. await setEmailState(null);
  1248. }
  1249. broadcastDataUpdate({ currentHotmailAccountId: null });
  1250. }
  1251. }
  1252. async function deleteHotmailAccounts(mode = 'all') {
  1253. const state = await getState();
  1254. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  1255. const targets = filterHotmailAccountsByUsage(accounts, mode);
  1256. const targetIds = new Set(targets.map((account) => account.id));
  1257. const nextAccounts = mode === 'used'
  1258. ? accounts.filter((account) => !targetIds.has(account.id))
  1259. : [];
  1260. await syncHotmailAccounts(nextAccounts);
  1261. if (state.currentHotmailAccountId && targetIds.has(state.currentHotmailAccountId)) {
  1262. await setState({ currentHotmailAccountId: null });
  1263. if (isHotmailProvider(state)) {
  1264. await setEmailState(null);
  1265. }
  1266. broadcastDataUpdate({ currentHotmailAccountId: null });
  1267. }
  1268. return {
  1269. deletedCount: targets.length,
  1270. remainingCount: nextAccounts.length,
  1271. };
  1272. }
  1273. async function patchHotmailAccount(accountId, updates = {}) {
  1274. const state = await getState();
  1275. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  1276. const account = findHotmailAccount(accounts, accountId);
  1277. if (!account) {
  1278. throw new Error('未找到对应的 Hotmail 账号。');
  1279. }
  1280. const nextAccount = normalizeHotmailAccount({
  1281. ...account,
  1282. ...updates,
  1283. id: account.id,
  1284. });
  1285. await syncHotmailAccounts(accounts.map((item) => (item.id === account.id ? nextAccount : item)));
  1286. if (state.currentHotmailAccountId === account.id && shouldClearHotmailCurrentSelection(nextAccount)) {
  1287. await setState({ currentHotmailAccountId: null });
  1288. broadcastDataUpdate({ currentHotmailAccountId: null });
  1289. if (isHotmailProvider(state)) {
  1290. await setEmailState(null);
  1291. }
  1292. }
  1293. return nextAccount;
  1294. }
  1295. async function setCurrentHotmailAccount(accountId, options = {}) {
  1296. const { markUsed = false, syncEmail = true } = options;
  1297. const state = await getState();
  1298. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  1299. const account = findHotmailAccount(accounts, accountId);
  1300. if (!account) {
  1301. throw new Error('未找到对应的 Hotmail 账号。');
  1302. }
  1303. if (markUsed) {
  1304. account.lastUsedAt = Date.now();
  1305. await syncHotmailAccounts(accounts.map((item) => (item.id === account.id ? account : item)));
  1306. }
  1307. await setState({ currentHotmailAccountId: account.id });
  1308. broadcastDataUpdate({ currentHotmailAccountId: account.id });
  1309. if (syncEmail) {
  1310. await setEmailState(account.email || null);
  1311. }
  1312. return account;
  1313. }
  1314. async function ensureHotmailAccountForFlow(options = {}) {
  1315. const { allowAllocate = true, markUsed = false, preferredAccountId = null } = options;
  1316. const state = await getState();
  1317. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  1318. const isAccountAllocatable = (candidate) => Boolean(candidate)
  1319. && candidate.status === 'authorized'
  1320. && !candidate.used
  1321. && Boolean(candidate.refreshToken);
  1322. let account = null;
  1323. if (preferredAccountId) {
  1324. account = findHotmailAccount(accounts, preferredAccountId);
  1325. }
  1326. if (!account && state.currentHotmailAccountId) {
  1327. account = findHotmailAccount(accounts, state.currentHotmailAccountId);
  1328. }
  1329. if ((!account || !isAccountAllocatable(account)) && allowAllocate) {
  1330. account = pickHotmailAccountForRun(accounts, {});
  1331. }
  1332. if (!account) {
  1333. throw new Error('没有可用的 Hotmail 账号。请先在侧边栏添加至少一个带刷新令牌(refresh token)的账号。');
  1334. }
  1335. if (!isAccountAllocatable(account)) {
  1336. throw new Error(`Hotmail 账号 ${account.email || account.id} 尚未就绪,无法读取邮件。`);
  1337. }
  1338. return setCurrentHotmailAccount(account.id, { markUsed, syncEmail: true });
  1339. }
  1340. function buildHotmailLocalEndpoint(baseUrl, path) {
  1341. const normalizedBaseUrl = normalizeHotmailLocalBaseUrl(baseUrl);
  1342. return new URL(path, `${normalizedBaseUrl}/`).toString();
  1343. }
  1344. async function requestHotmailRemoteMailbox(account, mailbox = 'INBOX') {
  1345. if (!account?.email) {
  1346. throw new Error('Hotmail 账号缺少邮箱地址。');
  1347. }
  1348. if (!account?.clientId) {
  1349. throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少客户端 ID。`);
  1350. }
  1351. if (!account?.refreshToken) {
  1352. throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`);
  1353. }
  1354. const { timeoutMs } = getHotmailMailApiRequestConfig();
  1355. const controller = new AbortController();
  1356. const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
  1357. try {
  1358. const result = await fetchMicrosoftMailboxMessages({
  1359. clientId: account.clientId,
  1360. refreshToken: account.refreshToken,
  1361. mailbox,
  1362. top: 10,
  1363. signal: controller.signal,
  1364. });
  1365. return {
  1366. mailbox,
  1367. payload: {
  1368. source: 'microsoft-api',
  1369. transport: result.transport,
  1370. tokenStrategy: result.tokenStrategy,
  1371. },
  1372. messages: normalizeHotmailMailApiMessages(result.messages).map((message) => ({
  1373. ...message,
  1374. mailbox: message?.mailbox || mailbox,
  1375. })),
  1376. nextRefreshToken: result.nextRefreshToken,
  1377. };
  1378. } catch (err) {
  1379. if (err?.name === 'AbortError') {
  1380. throw new Error(`Hotmail API 对接请求超时(>${Math.round(timeoutMs / 1000)} 秒):${mailbox}`);
  1381. }
  1382. throw new Error(`Hotmail API 对接请求失败:${err.message}`);
  1383. } finally {
  1384. clearTimeout(timeoutId);
  1385. }
  1386. }
  1387. function applyHotmailApiResultToAccount(account, apiResult) {
  1388. const nextRefreshToken = String(apiResult?.nextRefreshToken || '').trim();
  1389. return {
  1390. ...account,
  1391. refreshToken: nextRefreshToken || account.refreshToken,
  1392. status: 'authorized',
  1393. lastAuthAt: Date.now(),
  1394. lastError: '',
  1395. };
  1396. }
  1397. function buildHotmailMailApiFailureAccount(account, errorMessage) {
  1398. return normalizeHotmailAccount({
  1399. ...account,
  1400. status: 'error',
  1401. lastError: String(errorMessage || ''),
  1402. });
  1403. }
  1404. async function fetchHotmailMailboxMessagesFromRemoteService(account, mailboxes = HOTMAIL_MAILBOXES) {
  1405. let workingAccount = normalizeHotmailAccount(account);
  1406. const mailboxResults = [];
  1407. try {
  1408. for (const mailbox of mailboxes) {
  1409. const result = await requestHotmailRemoteMailbox(workingAccount, mailbox);
  1410. workingAccount = applyHotmailApiResultToAccount(workingAccount, result);
  1411. mailboxResults.push({
  1412. mailbox,
  1413. count: result.messages.length,
  1414. messages: result.messages.map((message) => ({
  1415. ...message,
  1416. mailbox: message?.mailbox || mailbox,
  1417. })),
  1418. });
  1419. }
  1420. } catch (err) {
  1421. const failedAccount = buildHotmailMailApiFailureAccount(workingAccount, err.message);
  1422. await upsertHotmailAccount(failedAccount);
  1423. throw err;
  1424. }
  1425. const savedAccount = await upsertHotmailAccount(workingAccount);
  1426. return {
  1427. account: savedAccount,
  1428. mailboxResults,
  1429. messages: mailboxResults.flatMap((item) => item.messages),
  1430. };
  1431. }
  1432. async function requestHotmailLocalMessages(account, mailboxes = HOTMAIL_MAILBOXES) {
  1433. if (!account?.email) {
  1434. throw new Error('Hotmail 账号缺少邮箱地址。');
  1435. }
  1436. if (!account?.clientId) {
  1437. throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少客户端 ID。`);
  1438. }
  1439. if (!account?.refreshToken) {
  1440. throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`);
  1441. }
  1442. const serviceSettings = getHotmailServiceSettings(await getState());
  1443. const { timeoutMs } = getHotmailMailApiRequestConfig();
  1444. const requestTimeoutMs = Math.max(timeoutMs, HOTMAIL_LOCAL_HELPER_TIMEOUT_MS);
  1445. const controller = new AbortController();
  1446. const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), requestTimeoutMs);
  1447. let response;
  1448. try {
  1449. response = await fetch(buildHotmailLocalEndpoint(serviceSettings.localBaseUrl, '/messages'), {
  1450. method: 'POST',
  1451. headers: {
  1452. 'Content-Type': 'application/json',
  1453. Accept: 'application/json',
  1454. },
  1455. body: JSON.stringify({
  1456. email: account.email,
  1457. clientId: account.clientId,
  1458. refreshToken: account.refreshToken,
  1459. mailboxes,
  1460. top: 5,
  1461. }),
  1462. signal: controller.signal,
  1463. });
  1464. } catch (err) {
  1465. if (err?.name === 'AbortError') {
  1466. throw new Error(`Hotmail 本地助手请求超时(>${Math.round(requestTimeoutMs / 1000)} 秒)`);
  1467. }
  1468. throw new Error(`Hotmail 本地助手请求失败:${err.message}`);
  1469. } finally {
  1470. clearTimeout(timeoutId);
  1471. }
  1472. const text = await response.text();
  1473. let payload = {};
  1474. try {
  1475. payload = text ? JSON.parse(text) : {};
  1476. } catch {
  1477. payload = { raw: text };
  1478. }
  1479. if (!response.ok || payload?.ok === false) {
  1480. const errorText = payload?.error || payload?.message || text || `HTTP ${response.status}`;
  1481. throw new Error(`Hotmail 本地助手返回失败:${errorText}`);
  1482. }
  1483. const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
  1484. const normalizedMessages = normalizeHotmailMailApiMessages(rawMessages).map((message, index) => ({
  1485. ...message,
  1486. mailbox: rawMessages[index]?.mailbox || 'INBOX',
  1487. receivedTimestamp: Number(rawMessages[index]?.receivedTimestamp || 0) || 0,
  1488. }));
  1489. const mailboxResults = Array.isArray(payload?.mailboxResults)
  1490. ? payload.mailboxResults.map((item) => ({
  1491. mailbox: String(item?.mailbox || 'INBOX'),
  1492. count: Number(item?.count || 0),
  1493. messages: normalizedMessages.filter((message) => String(message.mailbox || 'INBOX') === String(item?.mailbox || 'INBOX')),
  1494. }))
  1495. : mailboxes.map((mailbox) => ({
  1496. mailbox,
  1497. count: normalizedMessages.filter((message) => String(message.mailbox || 'INBOX') === mailbox).length,
  1498. messages: normalizedMessages.filter((message) => String(message.mailbox || 'INBOX') === mailbox),
  1499. }));
  1500. const nextAccount = applyHotmailApiResultToAccount(account, {
  1501. nextRefreshToken: String(payload?.nextRefreshToken || '').trim(),
  1502. });
  1503. const savedAccount = await upsertHotmailAccount(nextAccount);
  1504. return {
  1505. account: savedAccount,
  1506. mailboxResults,
  1507. messages: normalizedMessages,
  1508. };
  1509. }
  1510. async function requestHotmailLocalCode(account, pollPayload = {}) {
  1511. if (!account?.email) {
  1512. throw new Error('Hotmail 账号缺少邮箱地址。');
  1513. }
  1514. if (!account?.clientId) {
  1515. throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少客户端 ID。`);
  1516. }
  1517. if (!account?.refreshToken) {
  1518. throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`);
  1519. }
  1520. const serviceSettings = getHotmailServiceSettings(await getState());
  1521. const { timeoutMs } = getHotmailMailApiRequestConfig();
  1522. const requestTimeoutMs = Math.max(timeoutMs, HOTMAIL_LOCAL_HELPER_TIMEOUT_MS);
  1523. const controller = new AbortController();
  1524. const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), requestTimeoutMs);
  1525. let response;
  1526. try {
  1527. response = await fetch(buildHotmailLocalEndpoint(serviceSettings.localBaseUrl, '/code'), {
  1528. method: 'POST',
  1529. headers: {
  1530. 'Content-Type': 'application/json',
  1531. Accept: 'application/json',
  1532. },
  1533. body: JSON.stringify({
  1534. email: account.email,
  1535. clientId: account.clientId,
  1536. refreshToken: account.refreshToken,
  1537. mailboxes: HOTMAIL_MAILBOXES,
  1538. top: 5,
  1539. senderFilters: pollPayload.senderFilters || [],
  1540. subjectFilters: pollPayload.subjectFilters || [],
  1541. excludeCodes: pollPayload.excludeCodes || [],
  1542. filterAfterTimestamp: Number(pollPayload.filterAfterTimestamp || 0) || 0,
  1543. }),
  1544. signal: controller.signal,
  1545. });
  1546. } catch (err) {
  1547. if (err?.name === 'AbortError') {
  1548. throw new Error(`Hotmail 本地助手请求超时(>${Math.round(requestTimeoutMs / 1000)} 秒)`);
  1549. }
  1550. throw new Error(`Hotmail 本地助手请求失败:${err.message}`);
  1551. } finally {
  1552. clearTimeout(timeoutId);
  1553. }
  1554. const text = await response.text();
  1555. let payload = {};
  1556. try {
  1557. payload = text ? JSON.parse(text) : {};
  1558. } catch {
  1559. payload = { raw: text };
  1560. }
  1561. if (!response.ok || payload?.ok === false) {
  1562. const errorText = payload?.error || payload?.message || text || `HTTP ${response.status}`;
  1563. throw new Error(`Hotmail 本地助手返回失败:${errorText}`);
  1564. }
  1565. const normalizedMessage = payload?.message
  1566. ? {
  1567. ...normalizeHotmailMailApiMessages([payload.message])[0],
  1568. mailbox: payload?.message?.mailbox || 'INBOX',
  1569. receivedTimestamp: Number(payload?.message?.receivedTimestamp || 0) || 0,
  1570. }
  1571. : null;
  1572. const nextAccount = applyHotmailApiResultToAccount(account, {
  1573. nextRefreshToken: String(payload?.nextRefreshToken || '').trim(),
  1574. });
  1575. const savedAccount = await upsertHotmailAccount(nextAccount);
  1576. return {
  1577. account: savedAccount,
  1578. code: String(payload?.code || ''),
  1579. message: normalizedMessage,
  1580. usedTimeFallback: Boolean(payload?.usedTimeFallback),
  1581. selectionSource: String(payload?.selectionSource || ''),
  1582. };
  1583. }
  1584. async function pollHotmailVerificationCodeViaLocalHelper(step, account, pollPayload = {}) {
  1585. const maxAttempts = Number(pollPayload.maxAttempts) || 5;
  1586. const intervalMs = Number(pollPayload.intervalMs) || 3000;
  1587. let workingAccount = account;
  1588. let lastError = null;
  1589. for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  1590. throwIfStopped();
  1591. try {
  1592. await addLog(`步骤 ${step}:正在通过本地助手轮询 Hotmail 验证码(${attempt}/${maxAttempts})...`, 'info');
  1593. const fetchResult = await requestHotmailLocalCode(workingAccount, pollPayload);
  1594. workingAccount = fetchResult.account;
  1595. if (fetchResult.code) {
  1596. const mailboxLabel = fetchResult.message?.mailbox || 'INBOX';
  1597. if (fetchResult.usedTimeFallback) {
  1598. await addLog(`步骤 ${step}:本地助手使用时间回退后命中 Hotmail ${mailboxLabel} 验证码。`, 'warn');
  1599. }
  1600. await addLog(`步骤 ${step}:已通过本地助手在 Hotmail ${mailboxLabel} 中找到验证码:${fetchResult.code}`, 'ok');
  1601. return {
  1602. ok: true,
  1603. code: fetchResult.code,
  1604. emailTimestamp: fetchResult.message?.receivedTimestamp || Date.now(),
  1605. mailId: fetchResult.message?.id || '',
  1606. };
  1607. }
  1608. lastError = new Error(`步骤 ${step}:本地助手暂未返回匹配验证码(${attempt}/${maxAttempts})。`);
  1609. await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info');
  1610. } catch (err) {
  1611. lastError = err;
  1612. await addLog(`步骤 ${step}:本地助手轮询 Hotmail 失败:${err.message}`, 'warn');
  1613. }
  1614. if (attempt < maxAttempts) {
  1615. await sleepWithStop(intervalMs);
  1616. }
  1617. }
  1618. throw lastError || new Error(`步骤 ${step}:本地助手未返回新的匹配验证码。`);
  1619. }
  1620. async function fetchHotmailMailboxMessages(account, mailboxes = HOTMAIL_MAILBOXES) {
  1621. const serviceSettings = getHotmailServiceSettings(await getState());
  1622. if (serviceSettings.mode === HOTMAIL_SERVICE_MODE_LOCAL) {
  1623. return requestHotmailLocalMessages(account, mailboxes);
  1624. }
  1625. return fetchHotmailMailboxMessagesFromRemoteService(account, mailboxes);
  1626. }
  1627. async function verifyHotmailAccount(accountId) {
  1628. const state = await getState();
  1629. const account = findHotmailAccount(state.hotmailAccounts, accountId);
  1630. if (!account) {
  1631. throw new Error('未找到需要校验的 Hotmail 账号。');
  1632. }
  1633. const result = await fetchHotmailMailboxMessages(account, ['INBOX']);
  1634. return {
  1635. account: result.account,
  1636. messageCount: result.mailboxResults[0]?.count || 0,
  1637. };
  1638. }
  1639. async function testHotmailAccountMailAccess(accountId) {
  1640. const state = await getState();
  1641. const account = findHotmailAccount(state.hotmailAccounts, accountId);
  1642. if (!account) {
  1643. throw new Error('未找到需要测试的 Hotmail 账号。');
  1644. }
  1645. const result = await fetchHotmailMailboxMessages(account, HOTMAIL_MAILBOXES);
  1646. const latestMessage = getLatestHotmailMessage(result.messages);
  1647. const latestCode = latestMessage ? extractVerificationCodeFromMessage(latestMessage) : null;
  1648. return {
  1649. account: result.account,
  1650. accountId: result.account.id,
  1651. email: result.account.email,
  1652. messageCount: result.messages.length,
  1653. latestSubject: latestMessage?.subject || '',
  1654. latestMailbox: latestMessage?.mailbox || '',
  1655. latestCode: latestCode || '',
  1656. inboxCount: result.mailboxResults.find((item) => item.mailbox === 'INBOX')?.count || 0,
  1657. junkCount: result.mailboxResults.find((item) => item.mailbox === 'Junk')?.count || 0,
  1658. };
  1659. }
  1660. async function pollHotmailVerificationCode(step, state, pollPayload = {}) {
  1661. await addLog(`步骤 ${step}:正在确定 Hotmail 收信账号...`, 'info');
  1662. let account = await ensureHotmailAccountForFlow({
  1663. allowAllocate: true,
  1664. markUsed: false,
  1665. preferredAccountId: state.currentHotmailAccountId || null,
  1666. });
  1667. await addLog(`步骤 ${step}:当前使用 Hotmail 账号 ${account.email} 轮询收件箱。`, 'info');
  1668. const serviceSettings = getHotmailServiceSettings(state);
  1669. if (serviceSettings.mode === HOTMAIL_SERVICE_MODE_LOCAL) {
  1670. return pollHotmailVerificationCodeViaLocalHelper(step, account, pollPayload);
  1671. }
  1672. const maxAttempts = Number(pollPayload.maxAttempts) || 5;
  1673. const intervalMs = Number(pollPayload.intervalMs) || 3000;
  1674. let lastError = null;
  1675. function summarizeMessagesForLog(messages) {
  1676. return (messages || [])
  1677. .slice()
  1678. .sort((left, right) => {
  1679. const leftTime = Date.parse(left.receivedDateTime || '') || 0;
  1680. const rightTime = Date.parse(right.receivedDateTime || '') || 0;
  1681. return rightTime - leftTime;
  1682. })
  1683. .slice(0, 3)
  1684. .map((message) => {
  1685. const receivedAt = message?.receivedDateTime || '未知时间';
  1686. const sender = message?.from?.emailAddress?.address || '未知发件人';
  1687. const subject = message?.subject || '(无主题)';
  1688. const preview = String(message?.bodyPreview || '').replace(/\s+/g, ' ').trim().slice(0, 80);
  1689. return `[${message.mailbox || 'INBOX'}] ${receivedAt} | ${sender} | ${subject} | ${preview}`;
  1690. })
  1691. .join(' || ');
  1692. }
  1693. for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  1694. throwIfStopped();
  1695. try {
  1696. await addLog(`步骤 ${step}:正在通过 API对接 轮询 Hotmail 邮件(${attempt}/${maxAttempts})...`, 'info');
  1697. const fetchResult = await fetchHotmailMailboxMessages(account, HOTMAIL_MAILBOXES);
  1698. account = fetchResult.account;
  1699. const matchResult = pickVerificationMessageWithTimeFallback(fetchResult.messages, {
  1700. afterTimestamp: pollPayload.filterAfterTimestamp || 0,
  1701. senderFilters: pollPayload.senderFilters || [],
  1702. subjectFilters: pollPayload.subjectFilters || [],
  1703. excludeCodes: pollPayload.excludeCodes || [],
  1704. });
  1705. const match = matchResult.match;
  1706. if (match?.code) {
  1707. const mailboxLabel = match.message?.mailbox || 'INBOX';
  1708. if (matchResult.usedRelaxedFilters) {
  1709. const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配';
  1710. await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 Hotmail ${mailboxLabel} 验证码。`, 'warn');
  1711. }
  1712. await addLog(`步骤 ${step}:已通过 API对接 在 Hotmail ${mailboxLabel} 中找到验证码:${match.code}`, 'ok');
  1713. return {
  1714. ok: true,
  1715. code: match.code,
  1716. emailTimestamp: match.receivedAt || Date.now(),
  1717. mailId: match.message?.id || '',
  1718. };
  1719. }
  1720. lastError = new Error(`步骤 ${step}:暂未在 Hotmail 收件箱中找到匹配验证码(${attempt}/${maxAttempts})。`);
  1721. await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info');
  1722. const mailSummary = summarizeMessagesForLog(fetchResult.messages);
  1723. if (mailSummary) {
  1724. await addLog(`步骤 ${step}:最近邮件样本:${mailSummary}`, 'info');
  1725. }
  1726. } catch (err) {
  1727. lastError = err;
  1728. await addLog(`步骤 ${step}:Hotmail API 对接轮询失败:${err.message}`, 'warn');
  1729. }
  1730. if (attempt < maxAttempts) {
  1731. await sleepWithStop(intervalMs);
  1732. }
  1733. }
  1734. throw lastError || new Error(`步骤 ${step}:未在 Hotmail 收件箱中找到新的匹配验证码。`);
  1735. }
  1736. function generateRandomSuffix(length = 6) {
  1737. const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
  1738. let suffix = '';
  1739. for (let i = 0; i < length; i++) {
  1740. suffix += chars[Math.floor(Math.random() * chars.length)];
  1741. }
  1742. return suffix;
  1743. }
  1744. const GMAIL_ALIAS_WORDS = [
  1745. 'amber', 'apple', 'ash', 'berry', 'birch', 'blue', 'brook', 'cedar',
  1746. 'cloud', 'clover', 'coast', 'cocoa', 'coral', 'dawn', 'delta', 'echo',
  1747. 'ember', 'field', 'flint', 'flora', 'forest', 'frost', 'glade', 'harbor',
  1748. 'hazel', 'honey', 'ivory', 'jade', 'lake', 'leaf', 'light', 'lilac',
  1749. 'lotus', 'lunar', 'maple', 'meadow', 'mist', 'moon', 'nova', 'oasis',
  1750. 'olive', 'opal', 'pearl', 'pine', 'pixel', 'plum', 'quartz', 'rain',
  1751. 'raven', 'river', 'rose', 'sage', 'shore', 'sky', 'solar', 'spark',
  1752. 'stone', 'storm', 'sun', 'terra', 'vale', 'wave', 'willow', 'zephyr',
  1753. ];
  1754. function generateRandomWordAliasTag(parts = 3) {
  1755. const selected = [];
  1756. for (let i = 0; i < parts; i++) {
  1757. selected.push(GMAIL_ALIAS_WORDS[Math.floor(Math.random() * GMAIL_ALIAS_WORDS.length)]);
  1758. }
  1759. return selected.join('');
  1760. }
  1761. function parseGmailBaseEmail(rawValue) {
  1762. const value = String(rawValue || '').trim().toLowerCase();
  1763. const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i);
  1764. if (!match) return null;
  1765. return {
  1766. localPart: match[1],
  1767. domain: match[2].toLowerCase(),
  1768. };
  1769. }
  1770. function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
  1771. const provider = typeof stateOrProvider === 'string'
  1772. ? stateOrProvider
  1773. : stateOrProvider?.mailProvider;
  1774. const utils = (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null;
  1775. if (utils?.isManagedAliasProvider) {
  1776. return utils.isManagedAliasProvider(provider);
  1777. }
  1778. return provider === GMAIL_PROVIDER || provider === '2925';
  1779. }
  1780. function shouldUseCustomRegistrationEmail(state = {}) {
  1781. return isCustomMailProvider(state)
  1782. || (!isHotmailProvider(state)
  1783. && !isGeneratedAliasProvider(state)
  1784. && normalizeEmailGenerator(state.emailGenerator) === 'custom');
  1785. }
  1786. function buildGeneratedAliasEmail(state) {
  1787. const provider = state.mailProvider || '163';
  1788. const emailPrefix = (state.emailPrefix || '').trim();
  1789. if (provider === GMAIL_PROVIDER) {
  1790. if (!emailPrefix) {
  1791. throw new Error('Gmail 原邮箱未设置,请先在侧边栏填写。');
  1792. }
  1793. const parsed = parseGmailBaseEmail(emailPrefix);
  1794. if (!parsed) {
  1795. throw new Error('Gmail 原邮箱格式不正确,请填写类似 name@gmail.com 的地址。');
  1796. }
  1797. return `${parsed.localPart}+${generateRandomWordAliasTag()}@${parsed.domain}`;
  1798. }
  1799. if (!emailPrefix) {
  1800. throw new Error('2925 邮箱前缀未设置,请先在侧边栏填写。');
  1801. }
  1802. if (provider === '2925' && isGeneratedAliasProvider(state)) {
  1803. return `${emailPrefix}${generateRandomSuffix(6)}@2925.com`;
  1804. }
  1805. throw new Error(`未支持的别名邮箱类型:${provider}`);
  1806. }
  1807. function getManagedAliasUtils() {
  1808. return (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null;
  1809. }
  1810. function parseGmailBaseEmail(rawValue) {
  1811. const utils = getManagedAliasUtils();
  1812. if (utils?.parseManagedAliasBaseEmail) {
  1813. return utils.parseManagedAliasBaseEmail(rawValue, GMAIL_PROVIDER);
  1814. }
  1815. const value = String(rawValue || '').trim().toLowerCase();
  1816. const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i);
  1817. if (!match) return null;
  1818. return {
  1819. localPart: match[1],
  1820. domain: match[2].toLowerCase(),
  1821. };
  1822. }
  1823. function parseManagedAliasBaseEmail(rawValue, provider) {
  1824. const utils = getManagedAliasUtils();
  1825. if (utils?.parseManagedAliasBaseEmail) {
  1826. return utils.parseManagedAliasBaseEmail(rawValue, provider);
  1827. }
  1828. if (provider === GMAIL_PROVIDER) {
  1829. return parseGmailBaseEmail(rawValue);
  1830. }
  1831. const value = String(rawValue || '').trim().toLowerCase();
  1832. const match = value.match(/^([^@\s+]+)@(2925\.com)$/i);
  1833. if (!match) return null;
  1834. return {
  1835. localPart: match[1],
  1836. domain: match[2].toLowerCase(),
  1837. };
  1838. }
  1839. function isManagedAliasEmail(value, provider, baseEmail = '') {
  1840. const utils = getManagedAliasUtils();
  1841. if (utils?.isManagedAliasEmail) {
  1842. return utils.isManagedAliasEmail(value, provider, baseEmail);
  1843. }
  1844. const normalizedValue = String(value || '').trim().toLowerCase();
  1845. if (!normalizedValue) return false;
  1846. const parsedEmail = normalizedValue.match(/^([^@\s]+)@([^@\s]+\.[^@\s]+)$/);
  1847. if (!parsedEmail) return false;
  1848. const candidateLocalPart = parsedEmail[1];
  1849. const candidateDomain = parsedEmail[2];
  1850. if (provider === GMAIL_PROVIDER) {
  1851. if (!/^(?:gmail|googlemail)\.com$/i.test(candidateDomain)) {
  1852. return false;
  1853. }
  1854. const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
  1855. if (!parsedBaseEmail) {
  1856. return true;
  1857. }
  1858. return candidateDomain === parsedBaseEmail.domain
  1859. && candidateLocalPart.split('+')[0] === parsedBaseEmail.localPart;
  1860. }
  1861. if (provider !== '2925' || candidateDomain !== '2925.com') {
  1862. return false;
  1863. }
  1864. const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
  1865. if (!parsedBaseEmail) {
  1866. return true;
  1867. }
  1868. return candidateLocalPart === parsedBaseEmail.localPart || candidateLocalPart.startsWith(parsedBaseEmail.localPart);
  1869. }
  1870. function getManagedAliasBaseEmail(state = {}, provider = state?.mailProvider) {
  1871. const normalizedProvider = String(provider || '').trim().toLowerCase();
  1872. const legacyEmailPrefix = String(state?.emailPrefix || '').trim();
  1873. if (normalizedProvider === GMAIL_PROVIDER) {
  1874. const gmailBaseEmail = String(state?.gmailBaseEmail || '').trim();
  1875. if (gmailBaseEmail) {
  1876. return gmailBaseEmail;
  1877. }
  1878. return parseManagedAliasBaseEmail(legacyEmailPrefix, normalizedProvider) ? legacyEmailPrefix : '';
  1879. }
  1880. if (normalizedProvider === '2925') {
  1881. const mail2925BaseEmail = String(state?.mail2925BaseEmail || '').trim();
  1882. if (mail2925BaseEmail) {
  1883. return mail2925BaseEmail;
  1884. }
  1885. return parseManagedAliasBaseEmail(legacyEmailPrefix, normalizedProvider) ? legacyEmailPrefix : '';
  1886. }
  1887. return '';
  1888. }
  1889. function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
  1890. const provider = typeof stateOrProvider === 'string'
  1891. ? stateOrProvider
  1892. : stateOrProvider?.mailProvider;
  1893. const utils = getManagedAliasUtils();
  1894. if (utils?.isManagedAliasProvider) {
  1895. return utils.isManagedAliasProvider(provider);
  1896. }
  1897. return provider === GMAIL_PROVIDER || provider === '2925';
  1898. }
  1899. function shouldUseCustomRegistrationEmail(state = {}) {
  1900. return isCustomMailProvider(state)
  1901. || (!isHotmailProvider(state)
  1902. && !isGeneratedAliasProvider(state)
  1903. && normalizeEmailGenerator(state.emailGenerator) === 'custom');
  1904. }
  1905. function isReusableGeneratedAliasEmail(state = {}, email = state?.email) {
  1906. if (!isGeneratedAliasProvider(state)) {
  1907. return false;
  1908. }
  1909. return isManagedAliasEmail(email, state?.mailProvider, getManagedAliasBaseEmail(state));
  1910. }
  1911. function buildGeneratedAliasEmail(state) {
  1912. const provider = state.mailProvider || '163';
  1913. const baseEmail = getManagedAliasBaseEmail(state, provider);
  1914. const baseLabel = provider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱';
  1915. const exampleEmail = provider === GMAIL_PROVIDER ? 'name@gmail.com' : 'name@2925.com';
  1916. if (!baseEmail) {
  1917. throw new Error(`${baseLabel}未设置,请先在侧边栏填写,或直接在“注册邮箱”中手动填写完整邮箱。`);
  1918. }
  1919. if (!parseManagedAliasBaseEmail(baseEmail, provider)) {
  1920. throw new Error(`${baseLabel}格式不正确,请填写类似 ${exampleEmail} 的地址。`);
  1921. }
  1922. const utils = getManagedAliasUtils();
  1923. if (utils?.buildManagedAliasEmail) {
  1924. return utils.buildManagedAliasEmail(
  1925. provider,
  1926. baseEmail,
  1927. provider === GMAIL_PROVIDER ? generateRandomWordAliasTag() : generateRandomSuffix(6)
  1928. );
  1929. }
  1930. const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
  1931. if (provider === GMAIL_PROVIDER) {
  1932. return `${parsedBaseEmail.localPart}+${generateRandomWordAliasTag()}@${parsedBaseEmail.domain}`;
  1933. }
  1934. if (provider === '2925') {
  1935. return `${parsedBaseEmail.localPart}${generateRandomSuffix(6)}@${parsedBaseEmail.domain}`;
  1936. }
  1937. throw new Error(`未支持的别名邮箱类型:${provider}`);
  1938. }
  1939. function getLuckmailSessionConfig(state = {}) {
  1940. return {
  1941. apiKey: String(state.luckmailApiKey || ''),
  1942. baseUrl: normalizeLuckmailBaseUrl(state.luckmailBaseUrl),
  1943. emailType: normalizeLuckmailEmailType(state.luckmailEmailType),
  1944. domain: String(state.luckmailDomain || '').trim(),
  1945. };
  1946. }
  1947. function ensureLuckmailApiKey(state = {}) {
  1948. const apiKey = String(state.luckmailApiKey || '').trim();
  1949. if (!apiKey) {
  1950. throw new Error('LuckMail API Key 为空,请先在侧边栏填写。');
  1951. }
  1952. return apiKey;
  1953. }
  1954. async function requestLuckmail(method, path, { baseUrl, apiKey, params, jsonData, timeout = 30000 } = {}) {
  1955. const requestUrl = new URL(`${normalizeLuckmailBaseUrl(baseUrl)}${path}`);
  1956. if (params && typeof params === 'object') {
  1957. for (const [key, value] of Object.entries(params)) {
  1958. if (value === undefined || value === null || value === '') continue;
  1959. requestUrl.searchParams.set(key, String(value));
  1960. }
  1961. }
  1962. const controller = new AbortController();
  1963. const timeoutId = setTimeout(() => controller.abort(), timeout);
  1964. const headers = {
  1965. Accept: 'application/json',
  1966. };
  1967. if (apiKey) {
  1968. headers['X-API-Key'] = apiKey;
  1969. }
  1970. const upperMethod = String(method || 'GET').toUpperCase();
  1971. const fetchOptions = {
  1972. method: upperMethod,
  1973. headers,
  1974. signal: controller.signal,
  1975. };
  1976. if (jsonData !== undefined) {
  1977. headers['Content-Type'] = 'application/json';
  1978. fetchOptions.body = JSON.stringify(jsonData || {});
  1979. }
  1980. let response = null;
  1981. try {
  1982. response = await fetch(requestUrl.toString(), fetchOptions);
  1983. } catch (err) {
  1984. if (err?.name === 'AbortError') {
  1985. throw new Error(`LuckMail 请求超时:${path}`);
  1986. }
  1987. throw new Error(`LuckMail 请求失败:${err.message}`);
  1988. } finally {
  1989. clearTimeout(timeoutId);
  1990. }
  1991. let payload = null;
  1992. try {
  1993. payload = await response.json();
  1994. } catch {
  1995. throw new Error(`LuckMail 返回了无法解析的响应:${path}`);
  1996. }
  1997. if (!response.ok) {
  1998. const errorText = String(payload?.message || response.statusText || 'HTTP error');
  1999. throw new Error(`LuckMail 请求失败:${errorText}`);
  2000. }
  2001. if (!payload || typeof payload !== 'object') {
  2002. throw new Error(`LuckMail 返回数据无效:${path}`);
  2003. }
  2004. if (payload.code !== 0) {
  2005. const errorText = String(payload.message || 'Unknown error');
  2006. throw new Error(`LuckMail 接口返回失败:${errorText}`);
  2007. }
  2008. return payload.data;
  2009. }
  2010. function createLuckmailClient(state = {}) {
  2011. const config = getLuckmailSessionConfig(state);
  2012. const apiKey = ensureLuckmailApiKey(state);
  2013. const request = (method, path, options = {}) => requestLuckmail(method, path, {
  2014. baseUrl: config.baseUrl,
  2015. apiKey,
  2016. ...options,
  2017. });
  2018. return {
  2019. user: {
  2020. async purchaseEmails(projectCode, quantity, { emailType, domain } = {}) {
  2021. const body = {
  2022. project_code: projectCode,
  2023. quantity,
  2024. email_type: normalizeLuckmailEmailType(emailType),
  2025. };
  2026. if (domain) {
  2027. body.domain = String(domain).trim();
  2028. }
  2029. return request('POST', '/api/v1/openapi/email/purchase', {
  2030. jsonData: body,
  2031. });
  2032. },
  2033. async getPurchases({ page = 1, pageSize = 100, projectId, tagId, keyword, userDisabled } = {}) {
  2034. return normalizeLuckmailPurchaseListPage(await request('GET', '/api/v1/openapi/email/purchases', {
  2035. params: {
  2036. page,
  2037. page_size: pageSize,
  2038. project_id: projectId,
  2039. tag_id: tagId,
  2040. keyword,
  2041. user_disabled: userDisabled,
  2042. },
  2043. }));
  2044. },
  2045. async getTokenCode(token) {
  2046. return normalizeLuckmailTokenCode(await request(
  2047. 'GET',
  2048. `/api/v1/openapi/email/token/${encodeURIComponent(token)}/code`
  2049. ));
  2050. },
  2051. async checkTokenAlive(token) {
  2052. const data = await request(
  2053. 'GET',
  2054. `/api/v1/openapi/email/token/${encodeURIComponent(token)}/alive`
  2055. );
  2056. return {
  2057. email_address: String(data?.email_address || ''),
  2058. project: String(data?.project || ''),
  2059. alive: Boolean(data?.alive),
  2060. status: String(data?.status || ''),
  2061. message: String(data?.message || ''),
  2062. mail_count: Number(data?.mail_count) || 0,
  2063. };
  2064. },
  2065. async getTokenMails(token) {
  2066. const data = await request('GET', `/api/v1/openapi/email/token/${encodeURIComponent(token)}/mails`);
  2067. return {
  2068. email_address: String(data?.email_address || ''),
  2069. project: String(data?.project || ''),
  2070. warranty_until: String(data?.warranty_until || ''),
  2071. mails: normalizeLuckmailTokenMails(data?.mails || []),
  2072. };
  2073. },
  2074. async getTokenMailDetail(token, messageId) {
  2075. return normalizeLuckmailTokenMail(await request(
  2076. 'GET',
  2077. `/api/v1/openapi/email/token/${encodeURIComponent(token)}/mails/${encodeURIComponent(messageId)}`
  2078. ));
  2079. },
  2080. async setPurchaseDisabled(purchaseId, disabled) {
  2081. await request('PUT', `/api/v1/openapi/email/purchases/${encodeURIComponent(purchaseId)}/disabled`, {
  2082. jsonData: {
  2083. disabled: disabled ? 1 : 0,
  2084. },
  2085. });
  2086. },
  2087. async batchSetPurchaseDisabled(ids, disabled) {
  2088. await request('POST', '/api/v1/openapi/email/purchases/batch-disabled', {
  2089. jsonData: {
  2090. ids: (Array.isArray(ids) ? ids : []).map((id) => Number(id)).filter((id) => Number.isFinite(id) && id > 0),
  2091. disabled: disabled ? 1 : 0,
  2092. },
  2093. });
  2094. },
  2095. async setPurchaseTag(purchaseId, { tagId, tagName } = {}) {
  2096. const body = {};
  2097. if (tagId !== undefined) {
  2098. body.tag_id = Number(tagId) || 0;
  2099. }
  2100. if (tagName !== undefined) {
  2101. body.tag_name = String(tagName || '').trim();
  2102. }
  2103. await request('PUT', `/api/v1/openapi/email/purchases/${encodeURIComponent(purchaseId)}/tag`, {
  2104. jsonData: body,
  2105. });
  2106. },
  2107. async batchSetPurchaseTag(ids, { tagId, tagName } = {}) {
  2108. const body = {
  2109. ids: (Array.isArray(ids) ? ids : []).map((id) => Number(id)).filter((id) => Number.isFinite(id) && id > 0),
  2110. };
  2111. if (tagId !== undefined) {
  2112. body.tag_id = Number(tagId) || 0;
  2113. }
  2114. if (tagName !== undefined) {
  2115. body.tag_name = String(tagName || '').trim();
  2116. }
  2117. await request('POST', '/api/v1/openapi/email/purchases/batch-tag', {
  2118. jsonData: body,
  2119. });
  2120. },
  2121. async getTags() {
  2122. return normalizeLuckmailTags(await request('GET', '/api/v1/openapi/email/tags'));
  2123. },
  2124. async createTag(name, limitType, remark) {
  2125. const body = {
  2126. name: String(name || '').trim(),
  2127. limit_type: Number(limitType) || 0,
  2128. };
  2129. if (remark !== undefined) {
  2130. body.remark = String(remark || '').trim();
  2131. }
  2132. return normalizeLuckmailTags([await request('POST', '/api/v1/openapi/email/tags', {
  2133. jsonData: body,
  2134. })])[0] || null;
  2135. },
  2136. },
  2137. };
  2138. }
  2139. function getCurrentLuckmailPurchase(state = {}) {
  2140. return state.currentLuckmailPurchase
  2141. ? normalizeLuckmailPurchase(state.currentLuckmailPurchase)
  2142. : null;
  2143. }
  2144. function buildLuckmailPurchaseView(purchase, state = {}) {
  2145. const normalizedPurchase = normalizeLuckmailPurchase(purchase);
  2146. const usedPurchases = getLuckmailUsedPurchases(state);
  2147. const preserveTagInfo = getLuckmailPreserveTagInfo(state);
  2148. return {
  2149. id: normalizedPurchase.id,
  2150. email_address: normalizedPurchase.email_address,
  2151. project_name: normalizeLuckmailProjectName(normalizedPurchase.project_name) || DEFAULT_LUCKMAIL_PROJECT_CODE,
  2152. price: normalizedPurchase.price,
  2153. status: normalizedPurchase.status,
  2154. tag_id: normalizedPurchase.tag_id,
  2155. tag_name: normalizedPurchase.tag_name,
  2156. user_disabled: normalizedPurchase.user_disabled,
  2157. warranty_hours: normalizedPurchase.warranty_hours,
  2158. warranty_until: normalizedPurchase.warranty_until,
  2159. created_at: normalizedPurchase.created_at,
  2160. used: Boolean(usedPurchases[normalizeLuckmailPurchaseId(normalizedPurchase.id)]),
  2161. preserved: isLuckmailPurchasePreserved(normalizedPurchase, {
  2162. preserveTagId: preserveTagInfo.id,
  2163. preserveTagName: preserveTagInfo.name,
  2164. }),
  2165. disabled: normalizedPurchase.user_disabled === 1,
  2166. current: Number(getCurrentLuckmailPurchase(state)?.id) === normalizedPurchase.id,
  2167. reusable: isLuckmailPurchaseReusable(normalizedPurchase, {
  2168. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2169. usedPurchases,
  2170. preserveTagId: preserveTagInfo.id,
  2171. preserveTagName: preserveTagInfo.name,
  2172. now: Date.now(),
  2173. }),
  2174. };
  2175. }
  2176. async function getAllLuckmailPurchases(state, options = {}) {
  2177. const client = options.client || createLuckmailClient(state);
  2178. const pageSize = Math.max(1, Math.min(100, Number(options.pageSize) || 100));
  2179. const maxPages = Math.max(1, Number(options.maxPages) || 50);
  2180. const purchases = [];
  2181. for (let page = 1; page <= maxPages; page += 1) {
  2182. const pageResult = await client.user.getPurchases({
  2183. page,
  2184. pageSize,
  2185. keyword: options.keyword,
  2186. projectId: options.projectId,
  2187. tagId: options.tagId,
  2188. userDisabled: options.userDisabled,
  2189. });
  2190. const normalizedPage = normalizeLuckmailPurchaseListPage(pageResult);
  2191. purchases.push(...normalizedPage.list);
  2192. if (normalizedPage.list.length === 0) {
  2193. break;
  2194. }
  2195. if (normalizedPage.total > 0 && purchases.length >= normalizedPage.total) {
  2196. break;
  2197. }
  2198. if (normalizedPage.list.length < normalizedPage.page_size) {
  2199. break;
  2200. }
  2201. }
  2202. return purchases;
  2203. }
  2204. async function listLuckmailPurchasesByProject(state, options = {}) {
  2205. const projectCode = normalizeLuckmailProjectName(options.projectCode || DEFAULT_LUCKMAIL_PROJECT_CODE)
  2206. || DEFAULT_LUCKMAIL_PROJECT_CODE;
  2207. const purchases = await getAllLuckmailPurchases(state, options);
  2208. return purchases.filter((purchase) => isLuckmailPurchaseForProject(purchase, projectCode));
  2209. }
  2210. async function getLuckmailPurchaseById(state, purchaseId, options = {}) {
  2211. const normalizedPurchaseId = Number(normalizeLuckmailPurchaseId(purchaseId)) || 0;
  2212. if (!normalizedPurchaseId) {
  2213. throw new Error('LuckMail 邮箱 ID 无效。');
  2214. }
  2215. const purchases = await listLuckmailPurchasesByProject(state, options);
  2216. const purchase = purchases.find((item) => item.id === normalizedPurchaseId) || null;
  2217. if (!purchase) {
  2218. throw new Error(`未找到 ID=${normalizedPurchaseId} 的 openai LuckMail 邮箱。`);
  2219. }
  2220. return purchase;
  2221. }
  2222. async function listLuckmailPurchasesForManagement() {
  2223. const state = await getState();
  2224. const purchases = await listLuckmailPurchasesByProject(state, {
  2225. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2226. });
  2227. return purchases.map((purchase) => buildLuckmailPurchaseView(purchase, state));
  2228. }
  2229. async function ensureLuckmailPreserveTag(client, state = null) {
  2230. const resolvedState = state || await getState();
  2231. const preserveTagInfo = getLuckmailPreserveTagInfo(resolvedState);
  2232. if (preserveTagInfo.id > 0) {
  2233. return preserveTagInfo;
  2234. }
  2235. const tags = normalizeLuckmailTags(await client.user.getTags());
  2236. let preserveTag = tags.find(
  2237. (tag) => normalizeLuckmailProjectName(tag.name) === normalizeLuckmailProjectName(preserveTagInfo.name)
  2238. ) || null;
  2239. if (!preserveTag) {
  2240. preserveTag = await client.user.createTag(
  2241. DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  2242. 0,
  2243. '保留邮箱(不参与自动复用)'
  2244. );
  2245. }
  2246. await setLuckmailPreserveTagInfo(preserveTag);
  2247. return {
  2248. id: Number(preserveTag?.id) || 0,
  2249. name: String(preserveTag?.name || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  2250. };
  2251. }
  2252. async function activateLuckmailPurchaseForFlow(state, client, purchase, options = {}) {
  2253. const normalizedPurchase = normalizeLuckmailPurchase(purchase);
  2254. if (!normalizedPurchase?.email_address || !normalizedPurchase?.token) {
  2255. throw new Error('LuckMail 邮箱缺少 email/token,无法用于当前流程。');
  2256. }
  2257. let baselineCursor = null;
  2258. if (options.initializeCursor !== false) {
  2259. const mailList = await client.user.getTokenMails(normalizedPurchase.token);
  2260. baselineCursor = buildLuckmailBaselineCursor(mailList?.mails || []);
  2261. }
  2262. await setLuckmailPurchaseState(normalizedPurchase);
  2263. await setLuckmailMailCursorState(baselineCursor);
  2264. await setEmailState(normalizedPurchase.email_address);
  2265. if (options.logMessage) {
  2266. await addLog(options.logMessage, options.logLevel || 'ok');
  2267. }
  2268. return normalizedPurchase;
  2269. }
  2270. async function findReusableLuckmailPurchaseForFlow(state, client) {
  2271. const preserveTagInfo = getLuckmailPreserveTagInfo(state);
  2272. const reusablePurchases = filterReusableLuckmailPurchases(
  2273. await listLuckmailPurchasesByProject(state, {
  2274. client,
  2275. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2276. }),
  2277. {
  2278. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2279. usedPurchases: getLuckmailUsedPurchases(state),
  2280. preserveTagId: preserveTagInfo.id,
  2281. preserveTagName: preserveTagInfo.name,
  2282. now: Date.now(),
  2283. }
  2284. );
  2285. for (const candidate of reusablePurchases) {
  2286. try {
  2287. const aliveResult = await client.user.checkTokenAlive(candidate.token);
  2288. if (!aliveResult?.alive) {
  2289. await addLog(
  2290. `LuckMail:跳过不可复用邮箱 ${candidate.email_address}:${aliveResult?.message || aliveResult?.status || 'token 不可用'}`,
  2291. 'warn'
  2292. );
  2293. continue;
  2294. }
  2295. return candidate;
  2296. } catch (err) {
  2297. await addLog(`LuckMail:检测复用邮箱 ${candidate.email_address} 失败:${err.message}`, 'warn');
  2298. }
  2299. }
  2300. return null;
  2301. }
  2302. async function selectLuckmailPurchase(purchaseId) {
  2303. const state = await ensureManualInteractionAllowed('切换 LuckMail 邮箱');
  2304. const client = createLuckmailClient(state);
  2305. const purchase = await getLuckmailPurchaseById(state, purchaseId, {
  2306. client,
  2307. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2308. });
  2309. if (purchase.user_disabled === 1) {
  2310. throw new Error(`LuckMail 邮箱 ${purchase.email_address} 已禁用,无法使用。`);
  2311. }
  2312. const aliveResult = await client.user.checkTokenAlive(purchase.token);
  2313. if (!aliveResult?.alive) {
  2314. throw new Error(`LuckMail 邮箱 ${purchase.email_address} 当前不可用:${aliveResult?.message || aliveResult?.status || 'token 已失效'}`);
  2315. }
  2316. const activatedPurchase = await activateLuckmailPurchaseForFlow(state, client, purchase, {
  2317. initializeCursor: true,
  2318. logMessage: `LuckMail:已切换当前邮箱为 ${purchase.email_address}`,
  2319. });
  2320. const nextState = await getState();
  2321. return buildLuckmailPurchaseView(activatedPurchase, nextState);
  2322. }
  2323. async function setLuckmailPurchasePreservedState(purchaseId, preserved) {
  2324. const state = await ensureManualInteractionAllowed('设置 LuckMail 邮箱保留状态');
  2325. const client = createLuckmailClient(state);
  2326. const purchase = await getLuckmailPurchaseById(state, purchaseId, {
  2327. client,
  2328. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2329. });
  2330. if (preserved) {
  2331. const preserveTag = await ensureLuckmailPreserveTag(client, state);
  2332. await client.user.setPurchaseTag(purchase.id, { tagId: preserveTag.id });
  2333. } else {
  2334. await client.user.setPurchaseTag(purchase.id, { tagId: 0 });
  2335. }
  2336. await addLog(`LuckMail:已将 ${purchase.email_address} ${preserved ? '设为保留' : '取消保留'}`, 'ok');
  2337. const refreshedState = await getState();
  2338. const refreshedPurchase = await getLuckmailPurchaseById(refreshedState, purchase.id, {
  2339. client,
  2340. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2341. });
  2342. return buildLuckmailPurchaseView(refreshedPurchase, await getState());
  2343. }
  2344. async function setLuckmailPurchaseDisabledState(purchaseId, disabled) {
  2345. const state = await ensureManualInteractionAllowed(disabled ? '禁用 LuckMail 邮箱' : '启用 LuckMail 邮箱');
  2346. const client = createLuckmailClient(state);
  2347. const purchase = await getLuckmailPurchaseById(state, purchaseId, {
  2348. client,
  2349. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2350. });
  2351. await client.user.setPurchaseDisabled(purchase.id, disabled ? 1 : 0);
  2352. const currentPurchase = getCurrentLuckmailPurchase(await getState());
  2353. if (disabled && currentPurchase?.id === purchase.id) {
  2354. await clearLuckmailRuntimeState({ clearEmail: isLuckmailProvider(await getState()) });
  2355. }
  2356. await addLog(`LuckMail:已将 ${purchase.email_address} ${disabled ? '禁用' : '启用'}`, 'ok');
  2357. const refreshedState = await getState();
  2358. const refreshedPurchase = await getLuckmailPurchaseById(refreshedState, purchase.id, {
  2359. client,
  2360. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2361. });
  2362. return buildLuckmailPurchaseView(refreshedPurchase, await getState());
  2363. }
  2364. async function batchUpdateLuckmailPurchases(input = {}) {
  2365. const action = String(input.action || '').trim();
  2366. const selectedIds = Array.isArray(input.ids)
  2367. ? [...new Set(input.ids.map((id) => Number(normalizeLuckmailPurchaseId(id)) || 0).filter((id) => id > 0))]
  2368. : [];
  2369. if (!selectedIds.length) {
  2370. throw new Error('请先选择至少一个 LuckMail 邮箱。');
  2371. }
  2372. const state = await ensureManualInteractionAllowed('批量更新 LuckMail 邮箱');
  2373. const client = createLuckmailClient(state);
  2374. const purchases = await listLuckmailPurchasesByProject(state, {
  2375. client,
  2376. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2377. });
  2378. const purchaseMap = new Map(purchases.map((purchase) => [purchase.id, purchase]));
  2379. const targetPurchases = selectedIds.map((id) => purchaseMap.get(id)).filter(Boolean);
  2380. if (!targetPurchases.length) {
  2381. throw new Error('未找到可批量处理的 openai LuckMail 邮箱。');
  2382. }
  2383. const targetIds = targetPurchases.map((purchase) => purchase.id);
  2384. if (action === 'used' || action === 'unused') {
  2385. const nextUsedState = getLuckmailUsedPurchases(state);
  2386. targetIds.forEach((id) => {
  2387. const key = normalizeLuckmailPurchaseId(id);
  2388. if (!key) return;
  2389. if (action === 'used') {
  2390. nextUsedState[key] = true;
  2391. } else {
  2392. delete nextUsedState[key];
  2393. }
  2394. });
  2395. await setLuckmailUsedPurchasesState(nextUsedState);
  2396. await addLog(`LuckMail:已批量${action === 'used' ? '标记已用' : '标记未用'} ${targetIds.length} 个邮箱`, 'ok');
  2397. } else if (action === 'preserve' || action === 'unpreserve') {
  2398. if (action === 'preserve') {
  2399. const preserveTag = await ensureLuckmailPreserveTag(client, state);
  2400. await client.user.batchSetPurchaseTag(targetIds, { tagId: preserveTag.id });
  2401. } else {
  2402. await client.user.batchSetPurchaseTag(targetIds, { tagId: 0 });
  2403. }
  2404. await addLog(`LuckMail:已批量${action === 'preserve' ? '保留' : '取消保留'} ${targetIds.length} 个邮箱`, 'ok');
  2405. } else if (action === 'disable' || action === 'enable') {
  2406. await client.user.batchSetPurchaseDisabled(targetIds, action === 'disable' ? 1 : 0);
  2407. const currentPurchase = getCurrentLuckmailPurchase(await getState());
  2408. if (action === 'disable' && currentPurchase?.id && targetIds.includes(currentPurchase.id)) {
  2409. await clearLuckmailRuntimeState({ clearEmail: isLuckmailProvider(await getState()) });
  2410. }
  2411. await addLog(`LuckMail:已批量${action === 'disable' ? '禁用' : '启用'} ${targetIds.length} 个邮箱`, 'ok');
  2412. } else {
  2413. throw new Error(`不支持的 LuckMail 批量操作:${action}`);
  2414. }
  2415. return {
  2416. updatedIds: targetIds,
  2417. };
  2418. }
  2419. async function disableUsedLuckmailPurchases() {
  2420. const state = await ensureManualInteractionAllowed('禁用已用 LuckMail 邮箱');
  2421. const usedPurchases = getLuckmailUsedPurchases(state);
  2422. const preserveTagInfo = getLuckmailPreserveTagInfo(state);
  2423. const client = createLuckmailClient(state);
  2424. const purchases = await listLuckmailPurchasesByProject(state, {
  2425. client,
  2426. projectCode: DEFAULT_LUCKMAIL_PROJECT_CODE,
  2427. });
  2428. const targets = purchases.filter((purchase) => {
  2429. const purchaseId = normalizeLuckmailPurchaseId(purchase.id);
  2430. return Boolean(purchaseId && usedPurchases[purchaseId])
  2431. && !isLuckmailPurchasePreserved(purchase, {
  2432. preserveTagId: preserveTagInfo.id,
  2433. preserveTagName: preserveTagInfo.name,
  2434. })
  2435. && purchase.user_disabled !== 1;
  2436. });
  2437. if (!targets.length) {
  2438. return { disabledIds: [] };
  2439. }
  2440. const targetIds = targets.map((purchase) => purchase.id);
  2441. await client.user.batchSetPurchaseDisabled(targetIds, 1);
  2442. const currentPurchase = getCurrentLuckmailPurchase(await getState());
  2443. if (currentPurchase?.id && targetIds.includes(currentPurchase.id)) {
  2444. await clearLuckmailRuntimeState({ clearEmail: isLuckmailProvider(await getState()) });
  2445. }
  2446. await addLog(`LuckMail:已禁用 ${targetIds.length} 个本地已用邮箱`, 'ok');
  2447. return { disabledIds: targetIds };
  2448. }
  2449. async function ensureLuckmailPurchaseForFlow(options = {}) {
  2450. const { allowReuse = true } = options;
  2451. const state = await getState();
  2452. const existingPurchase = getCurrentLuckmailPurchase(state);
  2453. if (allowReuse && existingPurchase?.email_address && existingPurchase?.token) {
  2454. if (state.email !== existingPurchase.email_address) {
  2455. await setEmailState(existingPurchase.email_address);
  2456. }
  2457. return existingPurchase;
  2458. }
  2459. const config = getLuckmailSessionConfig(state);
  2460. const client = createLuckmailClient(state);
  2461. if (allowReuse) {
  2462. const reusablePurchase = await findReusableLuckmailPurchaseForFlow(state, client);
  2463. if (reusablePurchase) {
  2464. return activateLuckmailPurchaseForFlow(state, client, reusablePurchase, {
  2465. initializeCursor: true,
  2466. logMessage: `LuckMail:已复用 openai 邮箱 ${reusablePurchase.email_address}`,
  2467. });
  2468. }
  2469. }
  2470. const result = await client.user.purchaseEmails(DEFAULT_LUCKMAIL_PROJECT_CODE, 1, {
  2471. emailType: config.emailType,
  2472. domain: config.domain || undefined,
  2473. });
  2474. const purchases = normalizeLuckmailPurchases(result);
  2475. const purchase = purchases[0] || null;
  2476. if (!purchase?.email_address || !purchase?.token) {
  2477. throw new Error('LuckMail 购邮成功,但未返回可用邮箱或 token。');
  2478. }
  2479. return activateLuckmailPurchaseForFlow(state, client, purchase, {
  2480. initializeCursor: false,
  2481. logMessage: `LuckMail:已购买邮箱 ${purchase.email_address}(类型:${config.emailType},项目:${DEFAULT_LUCKMAIL_PROJECT_CODE})`,
  2482. });
  2483. }
  2484. async function resolveLuckmailVerificationMail(client, token, filters = {}, tokenCodeResult = null) {
  2485. const tokenCode = tokenCodeResult ? normalizeLuckmailTokenCode(tokenCodeResult) : null;
  2486. if (tokenCode?.mail) {
  2487. const tokenMail = tokenCode.verification_code && !tokenCode.mail.verification_code
  2488. ? {
  2489. ...tokenCode.mail,
  2490. verification_code: tokenCode.verification_code,
  2491. }
  2492. : tokenCode.mail;
  2493. const inlineMatch = pickLuckmailVerificationMail([tokenMail], filters);
  2494. if (inlineMatch) {
  2495. return inlineMatch;
  2496. }
  2497. }
  2498. const mailList = await client.user.getTokenMails(token);
  2499. let match = pickLuckmailVerificationMail(mailList.mails, filters);
  2500. if (match?.mail?.message_id && !match.mail.verification_code) {
  2501. const detail = await client.user.getTokenMailDetail(token, match.mail.message_id);
  2502. match = pickLuckmailVerificationMail([detail], filters);
  2503. }
  2504. return match || null;
  2505. }
  2506. async function pollLuckmailVerificationCode(step, state, pollPayload = {}) {
  2507. const purchase = getCurrentLuckmailPurchase(state);
  2508. if (!purchase?.token) {
  2509. throw new Error('LuckMail 当前没有可用 token,请先执行步骤 3 购买邮箱。');
  2510. }
  2511. const client = createLuckmailClient(state);
  2512. const maxAttempts = Math.max(1, Number(pollPayload.maxAttempts) || 5);
  2513. const intervalMs = Math.max(1000, Number(pollPayload.intervalMs) || 3000);
  2514. const filters = {
  2515. afterTimestamp: pollPayload.filterAfterTimestamp || 0,
  2516. senderFilters: pollPayload.senderFilters || [],
  2517. subjectFilters: pollPayload.subjectFilters || [],
  2518. excludeCodes: pollPayload.excludeCodes || [],
  2519. };
  2520. let lastError = null;
  2521. for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  2522. throwIfStopped();
  2523. await addLog(`步骤 ${step}:正在通过 LuckMail 轮询验证码(${attempt}/${maxAttempts})...`, 'info');
  2524. try {
  2525. const tokenCode = await client.user.getTokenCode(purchase.token);
  2526. const cursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
  2527. if (tokenCode.verification_code && tokenCode.mail && !isLuckmailMailNewerThanCursor(tokenCode.mail, cursor)) {
  2528. throw new Error(`步骤 ${step}:LuckMail 返回的最新邮件仍是旧验证码。`);
  2529. }
  2530. let match = null;
  2531. if (tokenCode.has_new_mail || tokenCode.verification_code) {
  2532. match = await resolveLuckmailVerificationMail(client, purchase.token, filters, tokenCode);
  2533. }
  2534. if (!match) {
  2535. match = await resolveLuckmailVerificationMail(client, purchase.token, filters, null);
  2536. }
  2537. if (match?.mail) {
  2538. const cursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
  2539. if (!isLuckmailMailNewerThanCursor(match.mail, cursor)) {
  2540. throw new Error(`步骤 ${step}:LuckMail 命中的邮件不是新邮件。`);
  2541. }
  2542. await setLuckmailMailCursorState(buildLuckmailMailCursor(match.mail));
  2543. return {
  2544. ok: true,
  2545. code: match.code,
  2546. emailTimestamp: normalizeLuckmailTimestamp(match.mail.received_at) || Date.now(),
  2547. mailId: match.mail.message_id,
  2548. };
  2549. }
  2550. lastError = new Error(`步骤 ${step}:暂未在 LuckMail 邮箱中找到新的匹配验证码。`);
  2551. } catch (err) {
  2552. if (isStopError(err)) {
  2553. throw err;
  2554. }
  2555. lastError = err;
  2556. await addLog(`步骤 ${step}:LuckMail 轮询失败:${err.message}`, 'warn');
  2557. }
  2558. if (attempt < maxAttempts) {
  2559. await sleepWithStop(intervalMs);
  2560. }
  2561. }
  2562. throw lastError || new Error(`步骤 ${step}:未在 LuckMail 邮箱中找到新的匹配验证码。`);
  2563. }
  2564. function summarizeCloudflareTempEmailMessagesForLog(messages) {
  2565. return (messages || [])
  2566. .slice()
  2567. .sort((left, right) => {
  2568. const leftTime = Date.parse(left.receivedDateTime || '') || 0;
  2569. const rightTime = Date.parse(right.receivedDateTime || '') || 0;
  2570. return rightTime - leftTime;
  2571. })
  2572. .slice(0, 3)
  2573. .map((message) => {
  2574. const receivedAt = message?.receivedDateTime || '未知时间';
  2575. const sender = message?.from?.emailAddress?.address || '未知发件人';
  2576. const subject = message?.subject || '(无主题)';
  2577. const preview = String(message?.bodyPreview || '').replace(/\s+/g, ' ').trim().slice(0, 80);
  2578. const address = message?.address || '未知地址';
  2579. return `[${address}] ${receivedAt} | ${sender} | ${subject} | ${preview}`;
  2580. })
  2581. .join(' || ');
  2582. }
  2583. async function deleteCloudflareTempEmailMail(config, mailId) {
  2584. const normalizedMailId = String(mailId || '').trim();
  2585. if (!normalizedMailId) return false;
  2586. await requestCloudflareTempEmailJson(config, `/admin/mails/${encodeURIComponent(normalizedMailId)}`, {
  2587. method: 'DELETE',
  2588. });
  2589. return true;
  2590. }
  2591. async function listCloudflareTempEmailMessages(state, options = {}) {
  2592. const config = ensureCloudflareTempEmailConfig(state, { requireAdminAuth: true });
  2593. const address = normalizeCloudflareTempEmailAddress(options.address);
  2594. const payload = await requestCloudflareTempEmailJson(config, '/admin/mails', {
  2595. method: 'GET',
  2596. searchParams: {
  2597. limit: Number(options.limit) || CLOUDFLARE_TEMP_EMAIL_DEFAULT_PAGE_SIZE,
  2598. offset: Number(options.offset) || 0,
  2599. address,
  2600. },
  2601. });
  2602. const messages = normalizeCloudflareTempEmailMailApiMessages(payload).filter((message) => {
  2603. if (!address) return true;
  2604. return !message.address || normalizeCloudflareTempEmailAddress(message.address) === address;
  2605. });
  2606. return { config, messages };
  2607. }
  2608. async function pollCloudflareTempEmailVerificationCode(step, state, pollPayload = {}) {
  2609. const config = ensureCloudflareTempEmailConfig(state, { requireAdminAuth: true });
  2610. const targetEmail = resolveCloudflareTempEmailPollTargetEmail(state, pollPayload, config);
  2611. const registrationEmail = normalizeCloudflareTempEmailReceiveMailbox(state.email);
  2612. if (!targetEmail) {
  2613. throw new Error('Cloudflare Temp Email 轮询前缺少目标邮箱地址,请先填写注册邮箱或“邮件接收”邮箱。');
  2614. }
  2615. if (registrationEmail && registrationEmail !== targetEmail) {
  2616. await addLog(`步骤 ${step}:正在轮询 Cloudflare Temp Email 收件邮箱(${targetEmail}),注册邮箱为 ${registrationEmail}...`, 'info');
  2617. } else {
  2618. await addLog(`步骤 ${step}:正在轮询 Cloudflare Temp Email 邮件(${targetEmail})...`, 'info');
  2619. }
  2620. const maxAttempts = Number(pollPayload.maxAttempts) || 5;
  2621. const intervalMs = Number(pollPayload.intervalMs) || 3000;
  2622. let lastError = null;
  2623. for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  2624. throwIfStopped();
  2625. try {
  2626. const { messages } = await listCloudflareTempEmailMessages(state, {
  2627. address: targetEmail,
  2628. limit: pollPayload.limit || CLOUDFLARE_TEMP_EMAIL_DEFAULT_PAGE_SIZE,
  2629. offset: pollPayload.offset || 0,
  2630. });
  2631. const matchResult = pickVerificationMessageWithTimeFallback(messages, {
  2632. afterTimestamp: pollPayload.filterAfterTimestamp || 0,
  2633. senderFilters: pollPayload.senderFilters || [],
  2634. subjectFilters: pollPayload.subjectFilters || [],
  2635. excludeCodes: pollPayload.excludeCodes || [],
  2636. });
  2637. const match = matchResult.match;
  2638. if (match?.code) {
  2639. if (matchResult.usedRelaxedFilters) {
  2640. const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配';
  2641. await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 Cloudflare Temp Email 验证码。`, 'warn');
  2642. }
  2643. try {
  2644. await deleteCloudflareTempEmailMail(config, match.message?.id);
  2645. } catch (err) {
  2646. await addLog(`步骤 ${step}:删除 Cloudflare Temp Email 邮件失败:${err.message}`, 'warn');
  2647. }
  2648. return {
  2649. ok: true,
  2650. code: match.code,
  2651. emailTimestamp: match.receivedAt || Date.now(),
  2652. mailId: match.message?.id || '',
  2653. };
  2654. }
  2655. lastError = new Error(`步骤 ${step}:暂未在 Cloudflare Temp Email 中找到匹配验证码(${attempt}/${maxAttempts})。`);
  2656. await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info');
  2657. const sample = summarizeCloudflareTempEmailMessagesForLog(messages);
  2658. if (sample) {
  2659. await addLog(`步骤 ${step}:最近邮件样本:${sample}`, 'info');
  2660. }
  2661. } catch (err) {
  2662. lastError = err;
  2663. await addLog(`步骤 ${step}:Cloudflare Temp Email 轮询失败:${err.message}`, 'warn');
  2664. }
  2665. if (attempt < maxAttempts) {
  2666. await sleepWithStop(intervalMs);
  2667. }
  2668. }
  2669. throw lastError || new Error(`步骤 ${step}:未在 Cloudflare Temp Email 中找到新的匹配验证码。`);
  2670. }
  2671. async function getOpenIcloudHostPreference() {
  2672. try {
  2673. const tabs = await chrome.tabs.query({
  2674. url: [
  2675. 'https://www.icloud.com/*',
  2676. 'https://www.icloud.com.cn/*',
  2677. ],
  2678. });
  2679. const activeTab = tabs.find((tab) => tab.active);
  2680. const candidates = activeTab ? [activeTab, ...tabs.filter((tab) => tab.id !== activeTab.id)] : tabs;
  2681. for (const tab of candidates) {
  2682. try {
  2683. const host = normalizeIcloudHost(new URL(tab.url).host);
  2684. if (host) return host;
  2685. } catch {}
  2686. }
  2687. } catch {}
  2688. return '';
  2689. }
  2690. async function getPreferredIcloudLoginUrl(error = null, state = null) {
  2691. const currentState = state || await getState();
  2692. const configuredHost = getConfiguredIcloudHostPreference(currentState);
  2693. if (configuredHost) {
  2694. return getIcloudLoginUrlForHost(configuredHost);
  2695. }
  2696. const messageHint = getIcloudHostHintFromMessage(getErrorMessage(error));
  2697. if (messageHint) {
  2698. return getIcloudLoginUrlForHost(messageHint);
  2699. }
  2700. const savedHost = normalizeIcloudHost(currentState?.preferredIcloudHost);
  2701. if (savedHost) {
  2702. return getIcloudLoginUrlForHost(savedHost);
  2703. }
  2704. const openHost = await getOpenIcloudHostPreference();
  2705. if (openHost) {
  2706. return getIcloudLoginUrlForHost(openHost);
  2707. }
  2708. return ICLOUD_LOGIN_URLS[0];
  2709. }
  2710. async function getPreferredIcloudSetupUrls(state = null, error = null) {
  2711. const preferredLoginUrl = await getPreferredIcloudLoginUrl(error, state);
  2712. const preferredHost = normalizeIcloudHost(new URL(preferredLoginUrl).host);
  2713. const preferredSetupUrl = getIcloudSetupUrlForHost(preferredHost);
  2714. if (!preferredSetupUrl) {
  2715. return [...ICLOUD_SETUP_URLS];
  2716. }
  2717. return [
  2718. preferredSetupUrl,
  2719. ...ICLOUD_SETUP_URLS.filter((url) => url !== preferredSetupUrl),
  2720. ];
  2721. }
  2722. function isIcloudLoginRequiredError(error) {
  2723. const message = getErrorMessage(error).toLowerCase();
  2724. return message.includes('could not validate icloud session')
  2725. || message.includes('hide my email service was unavailable')
  2726. || /\bstatus (401|403|409|421)\b/.test(message);
  2727. }
  2728. let lastIcloudLoginPromptAt = 0;
  2729. async function openIcloudLoginPage(preferredUrl) {
  2730. const tabs = await chrome.tabs.query({
  2731. url: [
  2732. 'https://www.icloud.com/*',
  2733. 'https://www.icloud.com.cn/*',
  2734. ],
  2735. });
  2736. const preferredHost = new URL(preferredUrl).host;
  2737. const existing = tabs.find((tab) => {
  2738. try {
  2739. return new URL(tab.url).host === preferredHost;
  2740. } catch {
  2741. return false;
  2742. }
  2743. });
  2744. if (existing?.id) {
  2745. await chrome.tabs.update(existing.id, { active: true });
  2746. if (existing.url !== preferredUrl) {
  2747. await chrome.tabs.update(existing.id, { url: preferredUrl });
  2748. }
  2749. return existing.id;
  2750. }
  2751. const created = await chrome.tabs.create({ url: preferredUrl, active: true });
  2752. return created.id;
  2753. }
  2754. async function promptIcloudLogin(error, actionLabel = 'iCloud 操作') {
  2755. const now = Date.now();
  2756. const preferredUrl = await getPreferredIcloudLoginUrl(error);
  2757. const originalError = getErrorMessage(error);
  2758. chrome.runtime.sendMessage({
  2759. type: 'ICLOUD_LOGIN_REQUIRED',
  2760. payload: {
  2761. actionLabel,
  2762. loginUrl: preferredUrl,
  2763. message: '需要先登录 iCloud,我已经为你打开登录页。',
  2764. detail: originalError,
  2765. },
  2766. }).catch(() => { });
  2767. if (now - lastIcloudLoginPromptAt < 15000) {
  2768. return;
  2769. }
  2770. lastIcloudLoginPromptAt = now;
  2771. await addLog(`iCloud:${actionLabel}时需要登录,正在打开 ${new URL(preferredUrl).host} ...`, 'warn');
  2772. try {
  2773. await openIcloudLoginPage(preferredUrl);
  2774. } catch (tabErr) {
  2775. await addLog(`iCloud:自动打开登录页失败:${getErrorMessage(tabErr)}`, 'warn');
  2776. }
  2777. }
  2778. async function withIcloudLoginHelp(actionLabel, action) {
  2779. try {
  2780. return await action();
  2781. } catch (err) {
  2782. if (isIcloudLoginRequiredError(err)) {
  2783. await promptIcloudLogin(err, actionLabel);
  2784. throw new Error('请先在新打开的 iCloud 页面中完成登录,再回来点击“我已登录”。');
  2785. }
  2786. throw err;
  2787. }
  2788. }
  2789. async function icloudRequest(method, url, options = {}) {
  2790. const { data } = options;
  2791. let response;
  2792. try {
  2793. response = await fetch(url, {
  2794. method,
  2795. credentials: 'include',
  2796. headers: data !== undefined ? { 'Content-Type': 'application/json' } : undefined,
  2797. body: data !== undefined ? JSON.stringify(data) : undefined,
  2798. });
  2799. } catch (err) {
  2800. throw new Error(`iCloud 请求失败:${method} ${url},${err.message}`);
  2801. }
  2802. if (!response.ok) {
  2803. throw new Error(`iCloud 请求失败:${method} ${url},status ${response.status}`);
  2804. }
  2805. try {
  2806. return await response.json();
  2807. } catch (err) {
  2808. throw new Error(`iCloud 返回的 JSON 无法解析:${method} ${url},${err.message}`);
  2809. }
  2810. }
  2811. async function validateIcloudSession(setupUrl) {
  2812. const data = await icloudRequest('POST', `${setupUrl}/validate`);
  2813. if (!data?.webservices?.premiummailsettings?.url) {
  2814. throw new Error('Could not validate iCloud session. Hide My Email service was unavailable.');
  2815. }
  2816. return data;
  2817. }
  2818. async function resolveIcloudPremiumMailService() {
  2819. const errors = [];
  2820. const state = await getState();
  2821. const setupUrls = await getPreferredIcloudSetupUrls(state);
  2822. for (const setupUrl of setupUrls) {
  2823. try {
  2824. const data = await validateIcloudSession(setupUrl);
  2825. const preferredIcloudHost = normalizeIcloudHost(new URL(setupUrl).host);
  2826. if (preferredIcloudHost && preferredIcloudHost !== normalizeIcloudHost(state.preferredIcloudHost)) {
  2827. await setState({ preferredIcloudHost });
  2828. }
  2829. return {
  2830. setupUrl,
  2831. serviceUrl: String(data.webservices.premiummailsettings.url || '').replace(/\/$/, ''),
  2832. };
  2833. } catch (err) {
  2834. errors.push(`${new URL(setupUrl).host}: ${getErrorMessage(err)}`);
  2835. }
  2836. }
  2837. throw new Error(errors.length
  2838. ? `Could not validate iCloud session. ${errors.join(' | ')}`
  2839. : 'Could not validate iCloud session. 请先在当前浏览器登录 icloud.com.cn 或 icloud.com。');
  2840. }
  2841. function getIcloudAliasLabel() {
  2842. const now = new Date();
  2843. const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
  2844. return `MultiPage ${dateStr}`;
  2845. }
  2846. async function checkIcloudSession() {
  2847. return withIcloudLoginHelp('检查 iCloud 会话', async () => {
  2848. const { setupUrl } = await resolveIcloudPremiumMailService();
  2849. await addLog(`iCloud:会话校验通过(${new URL(setupUrl).host})`, 'ok');
  2850. return { ok: true, setupUrl };
  2851. });
  2852. }
  2853. async function listIcloudAliases() {
  2854. return withIcloudLoginHelp('加载 iCloud 隐私邮箱列表', async () => {
  2855. const { serviceUrl } = await resolveIcloudPremiumMailService();
  2856. const response = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
  2857. const state = await getState();
  2858. return normalizeIcloudAliasList(response, {
  2859. usedEmails: getEffectiveUsedEmails(state),
  2860. preservedEmails: getPreservedAliasMap(state),
  2861. });
  2862. });
  2863. }
  2864. async function deleteIcloudAlias(payload) {
  2865. return withIcloudLoginHelp('删除 iCloud 隐私邮箱', async () => {
  2866. const alias = typeof payload === 'string'
  2867. ? { email: String(payload).trim().toLowerCase(), anonymousId: '' }
  2868. : {
  2869. email: String(payload?.email || '').trim().toLowerCase(),
  2870. anonymousId: String(payload?.anonymousId || '').trim(),
  2871. };
  2872. if (!alias.email) {
  2873. throw new Error('未提供需要删除的 iCloud 隐私邮箱。');
  2874. }
  2875. if (!alias.anonymousId) {
  2876. throw new Error(`缺少 ${alias.email} 的 anonymousId,请先刷新 iCloud 别名列表。`);
  2877. }
  2878. const { serviceUrl } = await resolveIcloudPremiumMailService();
  2879. try {
  2880. const directDelete = await icloudRequest('POST', `${serviceUrl}/v1/hme/delete`, {
  2881. data: { anonymousId: alias.anonymousId },
  2882. });
  2883. if (directDelete?.success === false) {
  2884. throw new Error(directDelete?.error?.errorMessage || 'delete failed');
  2885. }
  2886. } catch (err) {
  2887. await addLog(`iCloud:直接删除 ${alias.email} 失败,尝试先停用再删除...`, 'warn');
  2888. const deactivated = await icloudRequest('POST', `${serviceUrl}/v1/hme/deactivate`, {
  2889. data: { anonymousId: alias.anonymousId },
  2890. });
  2891. if (deactivated?.success === false) {
  2892. throw new Error(deactivated?.error?.errorMessage || `停用 ${alias.email} 失败`);
  2893. }
  2894. const deleted = await icloudRequest('POST', `${serviceUrl}/v1/hme/delete`, {
  2895. data: { anonymousId: alias.anonymousId },
  2896. });
  2897. if (deleted?.success === false) {
  2898. throw new Error(deleted?.error?.errorMessage || `删除 ${alias.email} 失败`);
  2899. }
  2900. }
  2901. const state = await getState();
  2902. const manualAliasUsage = getManualAliasUsageMap(state);
  2903. const preservedAliases = getPreservedAliasMap(state);
  2904. delete manualAliasUsage[alias.email];
  2905. delete preservedAliases[alias.email];
  2906. await setState({ manualAliasUsage, preservedAliases });
  2907. await addLog(`iCloud:已删除 ${alias.email}`, 'ok');
  2908. broadcastIcloudAliasesChanged({ reason: 'deleted', email: alias.email });
  2909. return { email: alias.email };
  2910. });
  2911. }
  2912. async function deleteUsedIcloudAliases() {
  2913. const aliases = await listIcloudAliases();
  2914. const usedAliases = aliases.filter((alias) => alias.used);
  2915. if (!usedAliases.length) {
  2916. return { deleted: [], skipped: [] };
  2917. }
  2918. const deleted = [];
  2919. const skipped = [];
  2920. for (const alias of usedAliases) {
  2921. if (alias.preserved) {
  2922. skipped.push({ email: alias.email, error: 'preserved' });
  2923. continue;
  2924. }
  2925. try {
  2926. await deleteIcloudAlias(alias);
  2927. deleted.push(alias.email);
  2928. } catch (err) {
  2929. skipped.push({ email: alias.email, error: getErrorMessage(err) });
  2930. }
  2931. }
  2932. return { deleted, skipped };
  2933. }
  2934. async function fetchIcloudHideMyEmail() {
  2935. return withIcloudLoginHelp('获取 iCloud 隐私邮箱', async () => {
  2936. throwIfStopped();
  2937. await addLog('iCloud:正在校验当前浏览器登录状态...', 'info');
  2938. const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
  2939. await addLog(`iCloud:已通过 ${new URL(setupUrl).host} 验证会话`, 'ok');
  2940. const existingAliasesResponse = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
  2941. const state = await getState();
  2942. const existingAliases = normalizeIcloudAliasList(existingAliasesResponse, {
  2943. usedEmails: getEffectiveUsedEmails(state),
  2944. preservedEmails: getPreservedAliasMap(state),
  2945. });
  2946. const reusableAlias = pickReusableIcloudAlias(existingAliases);
  2947. if (reusableAlias) {
  2948. await setEmailState(reusableAlias.email);
  2949. await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok');
  2950. broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email });
  2951. return reusableAlias.email;
  2952. }
  2953. await addLog('iCloud:没有可复用别名,开始生成新的 Hide My Email 地址...', 'warn');
  2954. const generated = await icloudRequest('POST', `${serviceUrl}/v1/hme/generate`);
  2955. if (!generated?.success || !generated?.result?.hme) {
  2956. throw new Error(generated?.error?.errorMessage || 'iCloud 隐私邮箱生成失败。');
  2957. }
  2958. const reserved = await icloudRequest('POST', `${serviceUrl}/v1/hme/reserve`, {
  2959. data: {
  2960. hme: generated.result.hme,
  2961. label: getIcloudAliasLabel(),
  2962. note: 'Generated through Multi-Page Automation',
  2963. },
  2964. });
  2965. if (!reserved?.success || !reserved?.result?.hme?.hme) {
  2966. throw new Error(reserved?.error?.errorMessage || 'iCloud 隐私邮箱保留失败。');
  2967. }
  2968. const alias = String(reserved.result.hme.hme || '').trim().toLowerCase();
  2969. await setEmailState(alias);
  2970. await addLog(`iCloud:已创建并保留新别名 ${alias}`, 'ok');
  2971. broadcastIcloudAliasesChanged({ reason: 'created', email: alias });
  2972. return alias;
  2973. });
  2974. }
  2975. async function finalizeIcloudAliasAfterSuccessfulFlow(state) {
  2976. const email = String(state?.email || '').trim().toLowerCase();
  2977. if (!email) {
  2978. return { handled: false, deleted: false };
  2979. }
  2980. const knownIcloudAlias = normalizeEmailGenerator(state?.emailGenerator) === 'icloud'
  2981. || Object.prototype.hasOwnProperty.call(getManualAliasUsageMap(state), email)
  2982. || Object.prototype.hasOwnProperty.call(getPreservedAliasMap(state), email);
  2983. if (!knownIcloudAlias) {
  2984. return { handled: false, deleted: false };
  2985. }
  2986. await setIcloudAliasUsedState({ email, used: true }, { silentLog: true });
  2987. await addLog(`iCloud:流程成功后已标记 ${email} 为已用。`, 'ok');
  2988. if (!state.autoDeleteUsedIcloudAlias) {
  2989. return { handled: true, deleted: false };
  2990. }
  2991. if (isAliasPreserved(state, email)) {
  2992. await addLog(`iCloud:${email} 已被标记为保留,跳过自动删除。`, 'info');
  2993. return { handled: true, deleted: false };
  2994. }
  2995. try {
  2996. const aliases = await listIcloudAliases();
  2997. const alias = findIcloudAliasByEmail(aliases, email);
  2998. if (!alias) {
  2999. await addLog(`iCloud:自动删除跳过,列表中未找到 ${email}。`, 'warn');
  3000. return { handled: true, deleted: false };
  3001. }
  3002. if (alias.preserved) {
  3003. await addLog(`iCloud:${email} 在最新别名列表中已是保留状态,跳过自动删除。`, 'info');
  3004. return { handled: true, deleted: false };
  3005. }
  3006. if (!alias.anonymousId) {
  3007. await addLog(`iCloud:自动删除跳过,${email} 缺少 anonymousId,请先刷新列表后重试。`, 'warn');
  3008. return { handled: true, deleted: false };
  3009. }
  3010. await deleteIcloudAlias(alias);
  3011. await addLog(`iCloud:流程成功后已自动删除 ${email}。`, 'ok');
  3012. return { handled: true, deleted: true };
  3013. } catch (err) {
  3014. await addLog(`iCloud:自动删除 ${email} 失败:${getErrorMessage(err)}`, 'warn');
  3015. return { handled: true, deleted: false };
  3016. }
  3017. }
  3018. // ============================================================
  3019. // Tab Registry
  3020. // ============================================================
  3021. async function getTabRegistry() {
  3022. return tabRuntime.getTabRegistry();
  3023. }
  3024. async function registerTab(source, tabId) {
  3025. return tabRuntime.registerTab(source, tabId);
  3026. }
  3027. async function isTabAlive(source) {
  3028. return tabRuntime.isTabAlive(source);
  3029. }
  3030. async function getTabId(source) {
  3031. return tabRuntime.getTabId(source);
  3032. }
  3033. function parseUrlSafely(rawUrl) {
  3034. if (typeof navigationUtils !== 'undefined' && navigationUtils?.parseUrlSafely) {
  3035. return navigationUtils.parseUrlSafely(rawUrl);
  3036. }
  3037. if (!rawUrl) return null;
  3038. try {
  3039. return new URL(rawUrl);
  3040. } catch {
  3041. return null;
  3042. }
  3043. }
  3044. function normalizeSub2ApiUrl(rawUrl) {
  3045. if (typeof navigationUtils !== 'undefined' && navigationUtils?.normalizeSub2ApiUrl) {
  3046. return navigationUtils.normalizeSub2ApiUrl(rawUrl);
  3047. }
  3048. const input = (rawUrl || '').trim() || DEFAULT_SUB2API_URL;
  3049. const withProtocol = /^https?:\/\//i.test(input) ? input : `https://${input}`;
  3050. const parsed = new URL(withProtocol);
  3051. if (!parsed.pathname || parsed.pathname === '/') {
  3052. parsed.pathname = '/admin/accounts';
  3053. }
  3054. parsed.hash = '';
  3055. return parsed.toString();
  3056. }
  3057. function getPanelMode(state = {}) {
  3058. if (typeof navigationUtils !== 'undefined' && navigationUtils?.getPanelMode) {
  3059. return navigationUtils.getPanelMode(state);
  3060. }
  3061. return state.panelMode === 'sub2api' ? 'sub2api' : 'cpa';
  3062. }
  3063. function getPanelModeLabel(modeOrState) {
  3064. if (typeof navigationUtils !== 'undefined' && navigationUtils?.getPanelModeLabel) {
  3065. return navigationUtils.getPanelModeLabel(modeOrState);
  3066. }
  3067. const mode = typeof modeOrState === 'string' ? modeOrState : getPanelMode(modeOrState);
  3068. return mode === 'sub2api' ? 'SUB2API' : 'CPA';
  3069. }
  3070. function isSignupPageHost(hostname = '') {
  3071. if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupPageHost) {
  3072. return navigationUtils.isSignupPageHost(hostname);
  3073. }
  3074. return ['auth0.openai.com', 'auth.openai.com', 'accounts.openai.com'].includes(hostname);
  3075. }
  3076. function isSignupEntryHost(hostname = '') {
  3077. if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupEntryHost) {
  3078. return navigationUtils.isSignupEntryHost(hostname);
  3079. }
  3080. return ['chatgpt.com', 'chat.openai.com'].includes(hostname);
  3081. }
  3082. function isSignupPasswordPageUrl(rawUrl) {
  3083. if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupPasswordPageUrl) {
  3084. return navigationUtils.isSignupPasswordPageUrl(rawUrl);
  3085. }
  3086. const parsed = parseUrlSafely(rawUrl);
  3087. if (!parsed) return false;
  3088. return isSignupPageHost(parsed.hostname)
  3089. && /\/create-account\/password(?:[/?#]|$)/i.test(parsed.pathname || '');
  3090. }
  3091. function isSignupEmailVerificationPageUrl(rawUrl) {
  3092. if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupEmailVerificationPageUrl) {
  3093. return navigationUtils.isSignupEmailVerificationPageUrl(rawUrl);
  3094. }
  3095. const parsed = parseUrlSafely(rawUrl);
  3096. if (!parsed) return false;
  3097. return isSignupPageHost(parsed.hostname)
  3098. && /\/email-verification(?:[/?#]|$)/i.test(parsed.pathname || '');
  3099. }
  3100. function is163MailHost(hostname = '') {
  3101. if (typeof navigationUtils !== 'undefined' && navigationUtils?.is163MailHost) {
  3102. return navigationUtils.is163MailHost(hostname);
  3103. }
  3104. return hostname === 'mail.163.com'
  3105. || hostname.endsWith('.mail.163.com')
  3106. || hostname === 'webmail.vip.163.com';
  3107. }
  3108. function isLocalhostOAuthCallbackUrl(rawUrl) {
  3109. if (typeof navigationUtils !== 'undefined' && navigationUtils?.isLocalhostOAuthCallbackUrl) {
  3110. return navigationUtils.isLocalhostOAuthCallbackUrl(rawUrl);
  3111. }
  3112. const parsed = parseUrlSafely(rawUrl);
  3113. if (!parsed) return false;
  3114. if (!['http:', 'https:'].includes(parsed.protocol)) return false;
  3115. if (!['localhost', '127.0.0.1'].includes(parsed.hostname)) return false;
  3116. if (!['/auth/callback', '/codex/callback'].includes(parsed.pathname)) return false;
  3117. const code = (parsed.searchParams.get('code') || '').trim();
  3118. const state = (parsed.searchParams.get('state') || '').trim();
  3119. return Boolean(code && state);
  3120. }
  3121. function isLocalCpaUrl(rawUrl) {
  3122. if (typeof navigationUtils !== 'undefined' && navigationUtils?.isLocalCpaUrl) {
  3123. return navigationUtils.isLocalCpaUrl(rawUrl);
  3124. }
  3125. const parsed = parseUrlSafely(rawUrl);
  3126. if (!parsed) return false;
  3127. if (!['http:', 'https:'].includes(parsed.protocol)) return false;
  3128. return ['localhost', '127.0.0.1'].includes(parsed.hostname);
  3129. }
  3130. function shouldBypassStep9ForLocalCpa(state) {
  3131. if (typeof navigationUtils !== 'undefined' && navigationUtils?.shouldBypassStep9ForLocalCpa) {
  3132. return navigationUtils.shouldBypassStep9ForLocalCpa(state);
  3133. }
  3134. return normalizeLocalCpaStep9Mode(state?.localCpaStep9Mode) === 'bypass'
  3135. && Boolean(state?.localhostUrl)
  3136. && isLocalCpaUrl(state?.vpsUrl);
  3137. }
  3138. function shouldSkipLoginVerificationForCpaCallback(state) {
  3139. if (typeof navigationUtils !== 'undefined' && navigationUtils?.shouldSkipLoginVerificationForCpaCallback) {
  3140. return navigationUtils.shouldSkipLoginVerificationForCpaCallback(state);
  3141. }
  3142. return getPanelMode(state) === 'cpa'
  3143. && normalizeCpaCallbackMode(state?.cpaCallbackMode) === 'step7';
  3144. }
  3145. function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
  3146. if (typeof navigationUtils !== 'undefined' && navigationUtils?.matchesSourceUrlFamily) {
  3147. return navigationUtils.matchesSourceUrlFamily(source, candidateUrl, referenceUrl);
  3148. }
  3149. const candidate = parseUrlSafely(candidateUrl);
  3150. if (!candidate) return false;
  3151. const reference = parseUrlSafely(referenceUrl);
  3152. switch (source) {
  3153. case 'signup-page':
  3154. return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
  3155. case 'duck-mail':
  3156. return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/');
  3157. case 'qq-mail':
  3158. return candidate.hostname === 'mail.qq.com' || candidate.hostname === 'wx.mail.qq.com';
  3159. case 'mail-163':
  3160. return is163MailHost(candidate.hostname);
  3161. case 'gmail-mail':
  3162. return candidate.hostname === 'mail.google.com';
  3163. case 'inbucket-mail':
  3164. return Boolean(reference) && candidate.origin === reference.origin && candidate.pathname.startsWith('/m/');
  3165. case 'mail-2925':
  3166. return candidate.hostname === '2925.com' || candidate.hostname === 'www.2925.com';
  3167. case 'vps-panel':
  3168. return Boolean(reference) && candidate.origin === reference.origin && candidate.pathname === reference.pathname;
  3169. case 'sub2api-panel':
  3170. return Boolean(reference)
  3171. && candidate.origin === reference.origin
  3172. && (candidate.pathname.startsWith('/admin/accounts') || candidate.pathname.startsWith('/login') || candidate.pathname === '/');
  3173. default:
  3174. return false;
  3175. }
  3176. }
  3177. async function rememberSourceLastUrl(source, url) {
  3178. return tabRuntime.rememberSourceLastUrl(source, url);
  3179. }
  3180. async function closeConflictingTabsForSource(source, currentUrl, options = {}) {
  3181. return tabRuntime.closeConflictingTabsForSource(source, currentUrl, options);
  3182. }
  3183. function isLocalhostOAuthCallbackTabMatch(callbackUrl, candidateUrl) {
  3184. return tabRuntime.isLocalhostOAuthCallbackTabMatch(callbackUrl, candidateUrl);
  3185. }
  3186. async function closeLocalhostCallbackTabs(callbackUrl, options = {}) {
  3187. return tabRuntime.closeLocalhostCallbackTabs(callbackUrl, options);
  3188. }
  3189. function buildLocalhostCleanupPrefix(rawUrl) {
  3190. return tabRuntime.buildLocalhostCleanupPrefix(rawUrl);
  3191. }
  3192. async function closeTabsByUrlPrefix(prefix, options = {}) {
  3193. return tabRuntime.closeTabsByUrlPrefix(prefix, options);
  3194. }
  3195. async function pingContentScriptOnTab(tabId) {
  3196. return tabRuntime.pingContentScriptOnTab(tabId);
  3197. }
  3198. async function waitForTabUrlFamily(source, tabId, referenceUrl, options = {}) {
  3199. return tabRuntime.waitForTabUrlFamily(source, tabId, referenceUrl, options);
  3200. }
  3201. async function waitForTabUrlMatch(tabId, matcher, options = {}) {
  3202. return tabRuntime.waitForTabUrlMatch(tabId, matcher, options);
  3203. }
  3204. async function waitForTabComplete(tabId, options = {}) {
  3205. return tabRuntime.waitForTabComplete(tabId, options);
  3206. }
  3207. async function ensureContentScriptReadyOnTab(source, tabId, options = {}) {
  3208. return tabRuntime.ensureContentScriptReadyOnTab(source, tabId, options);
  3209. }
  3210. // ============================================================
  3211. // Command Queue (for content scripts not yet ready)
  3212. // ============================================================
  3213. const pendingCommands = new Map(); // source -> { message, resolve, reject, timer }
  3214. function getContentScriptResponseTimeoutMs(message) {
  3215. return tabRuntime.getContentScriptResponseTimeoutMs(message);
  3216. }
  3217. function getMessageDebugLabel(source, message, tabId = null) {
  3218. return tabRuntime.getMessageDebugLabel(source, message, tabId);
  3219. }
  3220. function summarizeMessageResultForDebug(result) {
  3221. return tabRuntime.summarizeMessageResultForDebug(result);
  3222. }
  3223. function sendTabMessageWithTimeout(tabId, source, message, responseTimeoutMs = getContentScriptResponseTimeoutMs(message)) {
  3224. return tabRuntime.sendTabMessageWithTimeout(tabId, source, message, responseTimeoutMs);
  3225. }
  3226. function queueCommand(source, message, timeout = 15000) {
  3227. return tabRuntime.queueCommand(source, message, timeout);
  3228. }
  3229. function flushCommand(source, tabId) {
  3230. return tabRuntime.flushCommand(source, tabId);
  3231. }
  3232. function cancelPendingCommands(reason = STOP_ERROR_MESSAGE) {
  3233. return tabRuntime.cancelPendingCommands(reason);
  3234. }
  3235. // ============================================================
  3236. // Reuse or create tab
  3237. // ============================================================
  3238. async function reuseOrCreateTab(source, url, options = {}) {
  3239. return tabRuntime.reuseOrCreateTab(source, url, options);
  3240. }
  3241. // ============================================================
  3242. // Send command to content script (with readiness check)
  3243. // ============================================================
  3244. async function sendToContentScript(source, message, options = {}) {
  3245. return tabRuntime.sendToContentScript(source, message, options);
  3246. }
  3247. async function sendToContentScriptResilient(source, message, options = {}) {
  3248. return tabRuntime.sendToContentScriptResilient(source, message, options);
  3249. }
  3250. async function sendToMailContentScriptResilient(mail, message, options = {}) {
  3251. return tabRuntime.sendToMailContentScriptResilient(mail, message, options);
  3252. }
  3253. // ============================================================
  3254. // Logging
  3255. // ============================================================
  3256. async function addLog(message, level = 'info') {
  3257. if (typeof loggingStatus !== 'undefined' && loggingStatus?.addLog) {
  3258. return loggingStatus.addLog(message, level);
  3259. }
  3260. const state = await getState();
  3261. const logs = state.logs || [];
  3262. const entry = { message, level, timestamp: Date.now() };
  3263. logs.push(entry);
  3264. if (logs.length > 500) logs.splice(0, logs.length - 500);
  3265. await setState({ logs });
  3266. chrome.runtime.sendMessage({ type: 'LOG_ENTRY', payload: entry }).catch(() => { });
  3267. }
  3268. function getStep8CallbackUrlFromNavigation(details, signupTabId) {
  3269. if (typeof navigationUtils !== 'undefined' && navigationUtils?.getStep8CallbackUrlFromNavigation) {
  3270. return navigationUtils.getStep8CallbackUrlFromNavigation(details, signupTabId);
  3271. }
  3272. if (!Number.isInteger(signupTabId) || !details) return '';
  3273. if (details.tabId !== signupTabId) return '';
  3274. if (details.frameId !== 0) return '';
  3275. return isLocalhostOAuthCallbackUrl(details.url) ? details.url : '';
  3276. }
  3277. function getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId) {
  3278. if (typeof navigationUtils !== 'undefined' && navigationUtils?.getStep8CallbackUrlFromTabUpdate) {
  3279. return navigationUtils.getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId);
  3280. }
  3281. if (!Number.isInteger(signupTabId) || tabId !== signupTabId) return '';
  3282. const candidates = [changeInfo?.url, tab?.url];
  3283. for (const candidate of candidates) {
  3284. if (isLocalhostOAuthCallbackUrl(candidate)) return candidate;
  3285. }
  3286. return '';
  3287. }
  3288. function getSourceLabel(source) {
  3289. if (typeof loggingStatus !== 'undefined' && loggingStatus?.getSourceLabel) {
  3290. return loggingStatus.getSourceLabel(source);
  3291. }
  3292. const labels = {
  3293. 'gmail-mail': 'Gmail 邮箱',
  3294. 'sidepanel': '侧边栏',
  3295. 'signup-page': '认证页',
  3296. 'vps-panel': 'CPA 面板',
  3297. 'sub2api-panel': 'SUB2API 后台',
  3298. 'qq-mail': 'QQ 邮箱',
  3299. 'mail-163': '163 邮箱',
  3300. 'mail-2925': '2925 邮箱',
  3301. 'inbucket-mail': 'Inbucket 邮箱',
  3302. 'duck-mail': 'Duck 邮箱',
  3303. 'hotmail-api': 'Hotmail(API对接/本地助手)',
  3304. 'luckmail-api': 'LuckMail(API 购邮)',
  3305. 'cloudflare-temp-email': 'Cloudflare Temp Email',
  3306. };
  3307. return labels[source] || source || '未知来源';
  3308. }
  3309. // ============================================================
  3310. // Step Status Management
  3311. // ============================================================
  3312. async function setStepStatus(step, status) {
  3313. if (typeof loggingStatus !== 'undefined' && loggingStatus?.setStepStatus) {
  3314. return loggingStatus.setStepStatus(step, status);
  3315. }
  3316. const state = await getState();
  3317. const statuses = { ...state.stepStatuses };
  3318. statuses[step] = status;
  3319. await setState({ stepStatuses: statuses, currentStep: step });
  3320. chrome.runtime.sendMessage({
  3321. type: 'STEP_STATUS_CHANGED',
  3322. payload: { step, status },
  3323. }).catch(() => { });
  3324. }
  3325. function isStopError(error) {
  3326. const message = typeof error === 'string' ? error : error?.message;
  3327. return message === STOP_ERROR_MESSAGE;
  3328. }
  3329. function isRetryableContentScriptTransportError(error) {
  3330. const message = String(typeof error === 'string' ? error : error?.message || '');
  3331. return /back\/forward cache|message channel is closed|Receiving end does not exist|port closed before a response was received|A listener indicated an asynchronous response|did not respond in \d+s/i.test(message);
  3332. }
  3333. const navigationUtils = self.MultiPageBackgroundNavigationUtils?.createNavigationUtils({
  3334. DEFAULT_SUB2API_URL,
  3335. normalizeCpaCallbackMode,
  3336. normalizeLocalCpaStep9Mode,
  3337. });
  3338. const loggingStatus = self.MultiPageBackgroundLoggingStatus?.createLoggingStatus({
  3339. chrome,
  3340. DEFAULT_STATE,
  3341. getState,
  3342. isRecoverableStep9AuthFailure,
  3343. LOG_PREFIX,
  3344. setState,
  3345. STOP_ERROR_MESSAGE,
  3346. });
  3347. const tabRuntime = self.MultiPageBackgroundTabRuntime?.createTabRuntime({
  3348. addLog,
  3349. chrome,
  3350. getSourceLabel,
  3351. getState,
  3352. isLocalhostOAuthCallbackUrl,
  3353. isRetryableContentScriptTransportError,
  3354. LOG_PREFIX,
  3355. matchesSourceUrlFamily,
  3356. setState,
  3357. STOP_ERROR_MESSAGE,
  3358. throwIfStopped,
  3359. });
  3360. function getErrorMessage(error) {
  3361. if (typeof loggingStatus !== 'undefined' && loggingStatus?.getErrorMessage) {
  3362. return loggingStatus.getErrorMessage(error);
  3363. }
  3364. return String(typeof error === 'string' ? error : error?.message || '');
  3365. }
  3366. function isVerificationMailPollingError(error) {
  3367. if (typeof loggingStatus !== 'undefined' && loggingStatus?.isVerificationMailPollingError) {
  3368. return loggingStatus.isVerificationMailPollingError(error);
  3369. }
  3370. const message = getErrorMessage(error);
  3371. return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message);
  3372. }
  3373. function getLoginAuthStateLabel(state) {
  3374. if (typeof loggingStatus !== 'undefined' && loggingStatus?.getLoginAuthStateLabel) {
  3375. return loggingStatus.getLoginAuthStateLabel(state);
  3376. }
  3377. state = state === 'oauth_consent_page' ? 'unknown' : state;
  3378. switch (state) {
  3379. case 'verification_page': return '登录验证码页';
  3380. case 'password_page': return '密码页';
  3381. case 'email_page': return '邮箱输入页';
  3382. case 'login_timeout_error_page': return '登录超时报错页';
  3383. case 'oauth_consent_page': return 'OAuth 授权页';
  3384. case 'add_phone_page': return '手机号页';
  3385. default: return '未知页面';
  3386. }
  3387. }
  3388. function isRestartCurrentAttemptError(error) {
  3389. if (typeof loggingStatus !== 'undefined' && loggingStatus?.isRestartCurrentAttemptError) {
  3390. return loggingStatus.isRestartCurrentAttemptError(error);
  3391. }
  3392. const message = String(typeof error === 'string' ? error : error?.message || '');
  3393. return /当前邮箱已存在,需要重新开始新一轮/.test(message);
  3394. }
  3395. function isStep9RecoverableAuthError(error) {
  3396. const message = String(typeof error === 'string' ? error : error?.message || '');
  3397. return /STEP9_OAUTH_RETRY::/i.test(message)
  3398. || isRecoverableStep9AuthFailure(message);
  3399. }
  3400. function isLegacyStep9RecoverableAuthError(error) {
  3401. const message = String(typeof error === 'string' ? error : error?.message || '');
  3402. return /STEP9_OAUTH_TIMEOUT::|认证失败:\s*(?:Timeout waiting for OAuth callback|timeout of \d+ms exceeded)/i.test(message);
  3403. }
  3404. function isStepDoneStatus(status) {
  3405. return status === 'completed' || status === 'manual_completed' || status === 'skipped';
  3406. }
  3407. function getFirstUnfinishedStep(statuses = {}) {
  3408. if (typeof loggingStatus !== 'undefined' && loggingStatus?.getFirstUnfinishedStep) {
  3409. return loggingStatus.getFirstUnfinishedStep(statuses);
  3410. }
  3411. for (const step of STEP_IDS) {
  3412. if (!isStepDoneStatus(statuses[step] || 'pending')) return step;
  3413. }
  3414. return null;
  3415. }
  3416. function hasSavedProgress(statuses = {}) {
  3417. if (typeof loggingStatus !== 'undefined' && loggingStatus?.hasSavedProgress) {
  3418. return loggingStatus.hasSavedProgress(statuses);
  3419. }
  3420. return Object.values({ ...DEFAULT_STATE.stepStatuses, ...statuses }).some((status) => status !== 'pending');
  3421. }
  3422. function getDownstreamStateResets(step) {
  3423. if (step <= 1) {
  3424. return {
  3425. oauthUrl: null,
  3426. sub2apiSessionId: null,
  3427. sub2apiOAuthState: null,
  3428. sub2apiGroupId: null,
  3429. sub2apiDraftName: null,
  3430. flowStartTime: null,
  3431. password: null,
  3432. lastEmailTimestamp: null,
  3433. signupVerificationRequestedAt: null,
  3434. loginVerificationRequestedAt: null,
  3435. lastSignupCode: null,
  3436. lastLoginCode: null,
  3437. localhostUrl: null,
  3438. };
  3439. }
  3440. if (step === 2) {
  3441. return {
  3442. password: null,
  3443. lastEmailTimestamp: null,
  3444. signupVerificationRequestedAt: null,
  3445. loginVerificationRequestedAt: null,
  3446. lastSignupCode: null,
  3447. lastLoginCode: null,
  3448. localhostUrl: null,
  3449. };
  3450. }
  3451. if (step === 3 || step === 4) {
  3452. return {
  3453. lastEmailTimestamp: null,
  3454. signupVerificationRequestedAt: null,
  3455. loginVerificationRequestedAt: null,
  3456. lastSignupCode: null,
  3457. lastLoginCode: null,
  3458. localhostUrl: null,
  3459. };
  3460. }
  3461. if (step === 5 || step === 6 || step === 7 || step === 8) {
  3462. return {
  3463. lastLoginCode: null,
  3464. loginVerificationRequestedAt: null,
  3465. localhostUrl: null,
  3466. };
  3467. }
  3468. if (step === 9) {
  3469. return {
  3470. localhostUrl: null,
  3471. };
  3472. }
  3473. return {};
  3474. }
  3475. async function invalidateDownstreamAfterStepRestart(step, options = {}) {
  3476. const { logLabel = `步骤 ${step} 重新执行` } = options;
  3477. const state = await getState();
  3478. const statuses = { ...(state.stepStatuses || {}) };
  3479. const changedSteps = [];
  3480. for (let downstream = step + 1; downstream <= LAST_STEP_ID; downstream++) {
  3481. if (statuses[downstream] !== 'pending') {
  3482. statuses[downstream] = 'pending';
  3483. changedSteps.push(downstream);
  3484. }
  3485. }
  3486. if (changedSteps.length) {
  3487. await setState({ stepStatuses: statuses });
  3488. for (const downstream of changedSteps) {
  3489. chrome.runtime.sendMessage({
  3490. type: 'STEP_STATUS_CHANGED',
  3491. payload: { step: downstream, status: 'pending' },
  3492. }).catch(() => { });
  3493. }
  3494. await addLog(`${logLabel},已重置后续步骤状态:${changedSteps.join(', ')}`, 'warn');
  3495. }
  3496. const resets = getDownstreamStateResets(step);
  3497. if (Object.keys(resets).length) {
  3498. await setState(resets);
  3499. broadcastDataUpdate(resets);
  3500. }
  3501. }
  3502. function clearStopRequest() {
  3503. stopRequested = false;
  3504. }
  3505. function getRunningSteps(statuses = {}) {
  3506. if (typeof loggingStatus !== 'undefined' && loggingStatus?.getRunningSteps) {
  3507. return loggingStatus.getRunningSteps(statuses);
  3508. }
  3509. return Object.entries({ ...DEFAULT_STATE.stepStatuses, ...statuses })
  3510. .filter(([, status]) => status === 'running')
  3511. .map(([step]) => Number(step))
  3512. .sort((a, b) => a - b);
  3513. }
  3514. function getAutoRunStatusPayload(phase, payload = {}) {
  3515. const normalizedPayload = {
  3516. ...payload,
  3517. currentRun: payload.currentRun ?? autoRunCurrentRun,
  3518. totalRuns: payload.totalRuns ?? autoRunTotalRuns,
  3519. attemptRun: payload.attemptRun ?? autoRunAttemptRun,
  3520. };
  3521. if (typeof loggingStatus !== 'undefined' && loggingStatus?.getAutoRunStatusPayload) {
  3522. return loggingStatus.getAutoRunStatusPayload(phase, normalizedPayload);
  3523. }
  3524. return {
  3525. autoRunning: phase === 'scheduled'
  3526. || phase === 'running'
  3527. || phase === 'waiting_step'
  3528. || phase === 'waiting_email'
  3529. || phase === 'retrying'
  3530. || phase === 'waiting_interval',
  3531. autoRunPhase: phase,
  3532. autoRunCurrentRun: normalizedPayload.currentRun ?? 0,
  3533. autoRunTotalRuns: normalizedPayload.totalRuns ?? 1,
  3534. autoRunAttemptRun: normalizedPayload.attemptRun ?? 0,
  3535. scheduledAutoRunAt: Number.isFinite(Number(normalizedPayload.scheduledAt)) ? Number(normalizedPayload.scheduledAt) : null,
  3536. autoRunCountdownAt: Number.isFinite(Number(normalizedPayload.countdownAt)) ? Number(normalizedPayload.countdownAt) : null,
  3537. autoRunCountdownTitle: normalizedPayload.countdownTitle === undefined ? '' : String(normalizedPayload.countdownTitle || ''),
  3538. autoRunCountdownNote: normalizedPayload.countdownNote === undefined ? '' : String(normalizedPayload.countdownNote || ''),
  3539. };
  3540. }
  3541. async function broadcastAutoRunStatus(phase, payload = {}, extraState = {}) {
  3542. const rawScheduledAt = phase === 'scheduled'
  3543. ? (payload.scheduledAt ?? payload.scheduledAutoRunAt ?? null)
  3544. : null;
  3545. const rawCountdownAt = payload.countdownAt ?? payload.autoRunCountdownAt ?? null;
  3546. const statusPayload = {
  3547. phase,
  3548. currentRun: payload.currentRun ?? autoRunCurrentRun,
  3549. totalRuns: payload.totalRuns ?? autoRunTotalRuns,
  3550. attemptRun: payload.attemptRun ?? autoRunAttemptRun,
  3551. scheduledAt: rawScheduledAt === null ? null : Number(rawScheduledAt),
  3552. countdownAt: rawCountdownAt === null ? null : Number(rawCountdownAt),
  3553. countdownTitle: payload.countdownTitle === undefined ? '' : String(payload.countdownTitle || ''),
  3554. countdownNote: payload.countdownNote === undefined ? '' : String(payload.countdownNote || ''),
  3555. };
  3556. await setState({
  3557. ...extraState,
  3558. ...getAutoRunStatusPayload(phase, statusPayload),
  3559. });
  3560. chrome.runtime.sendMessage({
  3561. type: 'AUTO_RUN_STATUS',
  3562. payload: statusPayload,
  3563. }).catch(() => { });
  3564. }
  3565. function isAutoRunLockedState(state) {
  3566. return Boolean(state.autoRunning)
  3567. && (
  3568. state.autoRunPhase === 'running'
  3569. || state.autoRunPhase === 'waiting_step'
  3570. || state.autoRunPhase === 'retrying'
  3571. || state.autoRunPhase === 'waiting_interval'
  3572. );
  3573. }
  3574. function isAutoRunPausedState(state) {
  3575. return Boolean(state.autoRunning) && state.autoRunPhase === 'waiting_email';
  3576. }
  3577. function isAutoRunScheduledState(state) {
  3578. const plan = normalizeAutoRunTimerPlanFromState(state);
  3579. const scheduledAt = state.scheduledAutoRunAt === null ? null : Number(state.scheduledAutoRunAt);
  3580. return Boolean(state.autoRunning)
  3581. && state.autoRunPhase === 'scheduled'
  3582. && Number.isFinite(scheduledAt)
  3583. && plan?.kind === AUTO_RUN_TIMER_KIND_SCHEDULED_START;
  3584. }
  3585. function getPendingAutoRunTimerPlan(state = {}) {
  3586. return normalizeAutoRunTimerPlanFromState(state);
  3587. }
  3588. function formatAutoRunScheduleTime(timestamp) {
  3589. return new Date(timestamp).toLocaleString('zh-CN', {
  3590. hour12: false,
  3591. timeZone: DISPLAY_TIMEZONE,
  3592. month: '2-digit',
  3593. day: '2-digit',
  3594. hour: '2-digit',
  3595. minute: '2-digit',
  3596. second: '2-digit',
  3597. });
  3598. }
  3599. async function setAutoRunDelayEnabledState(enabled) {
  3600. const normalized = Boolean(enabled);
  3601. await setPersistentSettings({ autoRunDelayEnabled: normalized });
  3602. await setState({ autoRunDelayEnabled: normalized });
  3603. broadcastDataUpdate({ autoRunDelayEnabled: normalized });
  3604. }
  3605. async function ensureAutoRunTimerAlarm(fireAt) {
  3606. if (!Number.isFinite(fireAt) || fireAt <= Date.now()) {
  3607. return false;
  3608. }
  3609. const existingAlarm = await chrome.alarms.get(AUTO_RUN_TIMER_ALARM_NAME);
  3610. if (!existingAlarm || Math.abs((existingAlarm.scheduledTime || 0) - fireAt) > 1000) {
  3611. await chrome.alarms.clear(AUTO_RUN_TIMER_ALARM_NAME);
  3612. await chrome.alarms.create(AUTO_RUN_TIMER_ALARM_NAME, { when: fireAt });
  3613. }
  3614. return true;
  3615. }
  3616. async function clearAutoRunTimerAlarm() {
  3617. await chrome.alarms.clear(AUTO_RUN_TIMER_ALARM_NAME);
  3618. }
  3619. async function persistAutoRunTimerPlan(plan, extraState = {}) {
  3620. const normalizedPlan = normalizeAutoRunTimerPlan(plan);
  3621. if (!normalizedPlan) {
  3622. throw new Error('自动运行计时计划无效。');
  3623. }
  3624. const statusPayload = getAutoRunTimerStatusPayload(normalizedPlan);
  3625. await broadcastAutoRunStatus(
  3626. statusPayload.phase,
  3627. statusPayload,
  3628. {
  3629. ...extraState,
  3630. autoRunTimerPlan: normalizedPlan,
  3631. scheduledAutoRunPlan: null,
  3632. }
  3633. );
  3634. await ensureAutoRunTimerAlarm(normalizedPlan.fireAt);
  3635. return normalizedPlan;
  3636. }
  3637. function getAutoRunTimerResumeOptions(plan) {
  3638. const normalizedPlan = normalizeAutoRunTimerPlan(plan);
  3639. if (!normalizedPlan) {
  3640. return null;
  3641. }
  3642. if (normalizedPlan.kind === AUTO_RUN_TIMER_KIND_SCHEDULED_START) {
  3643. return {
  3644. loopOptions: {
  3645. autoRunSkipFailures: normalizedPlan.autoRunSkipFailures,
  3646. mode: normalizedPlan.mode,
  3647. },
  3648. statusPayload: {
  3649. currentRun: 0,
  3650. totalRuns: normalizedPlan.totalRuns,
  3651. attemptRun: 0,
  3652. },
  3653. };
  3654. }
  3655. if (normalizedPlan.kind === AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS) {
  3656. const nextRun = Math.min(normalizedPlan.currentRun + 1, normalizedPlan.totalRuns);
  3657. return {
  3658. loopOptions: {
  3659. autoRunSkipFailures: normalizedPlan.autoRunSkipFailures,
  3660. mode: 'restart',
  3661. resumeCurrentRun: nextRun,
  3662. resumeAttemptRun: 1,
  3663. resumeRoundSummaries: normalizedPlan.roundSummaries,
  3664. },
  3665. statusPayload: {
  3666. currentRun: nextRun,
  3667. totalRuns: normalizedPlan.totalRuns,
  3668. attemptRun: 1,
  3669. },
  3670. };
  3671. }
  3672. return {
  3673. loopOptions: {
  3674. autoRunSkipFailures: normalizedPlan.autoRunSkipFailures,
  3675. mode: 'restart',
  3676. resumeCurrentRun: normalizedPlan.currentRun,
  3677. resumeAttemptRun: normalizedPlan.attemptRun,
  3678. resumeRoundSummaries: normalizedPlan.roundSummaries,
  3679. },
  3680. statusPayload: {
  3681. currentRun: normalizedPlan.currentRun,
  3682. totalRuns: normalizedPlan.totalRuns,
  3683. attemptRun: normalizedPlan.attemptRun,
  3684. },
  3685. };
  3686. }
  3687. let autoRunTimerLaunching = false;
  3688. async function launchAutoRunTimerPlan(trigger = 'alarm', options = {}) {
  3689. const { expectedKinds = [] } = options;
  3690. if (autoRunTimerLaunching) {
  3691. return false;
  3692. }
  3693. autoRunTimerLaunching = true;
  3694. try {
  3695. const state = await getState();
  3696. const plan = getPendingAutoRunTimerPlan(state);
  3697. if (!plan) {
  3698. return false;
  3699. }
  3700. if (expectedKinds.length && !expectedKinds.includes(plan.kind)) {
  3701. return false;
  3702. }
  3703. if (autoRunActive) {
  3704. return false;
  3705. }
  3706. const resumeOptions = getAutoRunTimerResumeOptions(plan);
  3707. if (!resumeOptions) {
  3708. await clearAutoRunTimerAlarm();
  3709. await broadcastAutoRunStatus('idle', {
  3710. currentRun: 0,
  3711. totalRuns: 1,
  3712. attemptRun: 0,
  3713. }, {
  3714. autoRunRoundSummaries: [],
  3715. autoRunTimerPlan: null,
  3716. scheduledAutoRunPlan: null,
  3717. });
  3718. return false;
  3719. }
  3720. await clearAutoRunTimerAlarm();
  3721. autoRunCurrentRun = resumeOptions.statusPayload.currentRun;
  3722. autoRunTotalRuns = plan.totalRuns;
  3723. autoRunAttemptRun = resumeOptions.statusPayload.attemptRun;
  3724. if (plan.kind === AUTO_RUN_TIMER_KIND_SCHEDULED_START && trigger !== 'manual' && state.autoRunDelayEnabled) {
  3725. await setAutoRunDelayEnabledState(false);
  3726. }
  3727. await broadcastAutoRunStatus(
  3728. 'running',
  3729. resumeOptions.statusPayload,
  3730. {
  3731. autoRunSkipFailures: plan.autoRunSkipFailures,
  3732. autoRunRoundSummaries: serializeAutoRunRoundSummaries(plan.totalRuns, plan.roundSummaries),
  3733. autoRunTimerPlan: null,
  3734. scheduledAutoRunPlan: null,
  3735. }
  3736. );
  3737. clearStopRequest();
  3738. let logMessage = '倒计时结束,自动运行开始执行。';
  3739. if (plan.kind === AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS) {
  3740. logMessage = trigger === 'manual'
  3741. ? '已手动跳过线程间隔,自动流程立即开始下一轮。'
  3742. : '线程间隔结束,自动流程开始下一轮。';
  3743. } else if (plan.kind === AUTO_RUN_TIMER_KIND_BEFORE_RETRY) {
  3744. logMessage = trigger === 'manual'
  3745. ? `已手动跳过线程间隔,立即开始第 ${plan.currentRun}/${plan.totalRuns} 轮第 ${plan.attemptRun} 次尝试。`
  3746. : `线程间隔结束,开始第 ${plan.currentRun}/${plan.totalRuns} 轮第 ${plan.attemptRun} 次尝试。`;
  3747. } else if (trigger === 'manual') {
  3748. logMessage = '已手动跳过倒计时,自动运行立即开始。';
  3749. }
  3750. await addLog(logMessage, 'info');
  3751. startAutoRunLoop(plan.totalRuns, resumeOptions.loopOptions);
  3752. return true;
  3753. } finally {
  3754. autoRunTimerLaunching = false;
  3755. }
  3756. }
  3757. async function scheduleAutoRun(totalRuns, options = {}) {
  3758. const state = await getState();
  3759. if (isAutoRunLockedState(state) || isAutoRunPausedState(state) || autoRunActive) {
  3760. throw new Error('自动运行已在进行中,请先停止后再重新计划。');
  3761. }
  3762. if (getPendingAutoRunTimerPlan(state)) {
  3763. throw new Error('已有自动运行倒计时计划,请先取消或立即开始。');
  3764. }
  3765. const delayMinutes = normalizeAutoRunDelayMinutes(options.delayMinutes);
  3766. const timerPlan = normalizeAutoRunTimerPlan({
  3767. kind: AUTO_RUN_TIMER_KIND_SCHEDULED_START,
  3768. fireAt: Date.now() + delayMinutes * 60 * 1000,
  3769. totalRuns,
  3770. autoRunSkipFailures: options.autoRunSkipFailures,
  3771. mode: options.mode,
  3772. });
  3773. autoRunCurrentRun = 0;
  3774. autoRunTotalRuns = timerPlan.totalRuns;
  3775. autoRunAttemptRun = 0;
  3776. await persistAutoRunTimerPlan(timerPlan, {
  3777. autoRunSkipFailures: timerPlan.autoRunSkipFailures,
  3778. autoRunRoundSummaries: serializeAutoRunRoundSummaries(timerPlan.totalRuns, []),
  3779. });
  3780. await addLog(
  3781. `自动运行已计划:${delayMinutes} 分钟后启动(${formatAutoRunScheduleTime(timerPlan.fireAt)}),目标 ${timerPlan.totalRuns} 轮。`,
  3782. 'info'
  3783. );
  3784. return { ok: true, scheduledAt: timerPlan.fireAt };
  3785. }
  3786. async function cancelScheduledAutoRun(options = {}) {
  3787. const state = await getState();
  3788. const plan = getPendingAutoRunTimerPlan(state);
  3789. if (!plan || plan.kind !== AUTO_RUN_TIMER_KIND_SCHEDULED_START) {
  3790. return false;
  3791. }
  3792. autoRunCurrentRun = 0;
  3793. autoRunTotalRuns = plan.totalRuns;
  3794. autoRunAttemptRun = 0;
  3795. await broadcastAutoRunStatus(
  3796. 'idle',
  3797. {
  3798. currentRun: 0,
  3799. totalRuns: plan.totalRuns,
  3800. attemptRun: 0,
  3801. },
  3802. {
  3803. autoRunRoundSummaries: [],
  3804. autoRunTimerPlan: null,
  3805. scheduledAutoRunPlan: null,
  3806. }
  3807. );
  3808. await clearAutoRunTimerAlarm();
  3809. if (options.logMessage !== false) {
  3810. await addLog(options.logMessage || '已取消自动运行倒计时计划。', 'warn');
  3811. }
  3812. return true;
  3813. }
  3814. async function restoreAutoRunTimerIfNeeded() {
  3815. const state = await getState();
  3816. const plan = getPendingAutoRunTimerPlan(state);
  3817. if (!plan) {
  3818. if (state.autoRunPhase === 'scheduled' || state.autoRunPhase === 'waiting_interval') {
  3819. await clearAutoRunTimerAlarm();
  3820. await broadcastAutoRunStatus('idle', {
  3821. currentRun: 0,
  3822. totalRuns: 1,
  3823. attemptRun: 0,
  3824. }, {
  3825. autoRunRoundSummaries: [],
  3826. autoRunTimerPlan: null,
  3827. scheduledAutoRunPlan: null,
  3828. });
  3829. }
  3830. return;
  3831. }
  3832. if (plan.fireAt <= Date.now()) {
  3833. await launchAutoRunTimerPlan('restore');
  3834. return;
  3835. }
  3836. const statusPayload = getAutoRunTimerStatusPayload(plan);
  3837. await broadcastAutoRunStatus(
  3838. statusPayload.phase,
  3839. statusPayload,
  3840. {
  3841. autoRunSkipFailures: plan.autoRunSkipFailures,
  3842. autoRunRoundSummaries: serializeAutoRunRoundSummaries(plan.totalRuns, plan.roundSummaries),
  3843. autoRunTimerPlan: plan,
  3844. scheduledAutoRunPlan: null,
  3845. }
  3846. );
  3847. await ensureAutoRunTimerAlarm(plan.fireAt);
  3848. }
  3849. async function ensureManualInteractionAllowed(actionLabel) {
  3850. const state = await getState();
  3851. if (isAutoRunLockedState(state)) {
  3852. throw new Error(`自动流程运行中,请先停止后再${actionLabel}。`);
  3853. }
  3854. if (isAutoRunPausedState(state)) {
  3855. throw new Error(`自动流程当前已暂停。请点击“继续”,或先确认接管自动流程后再${actionLabel}。`);
  3856. }
  3857. if (isAutoRunScheduledState(state)) {
  3858. throw new Error(`自动流程已计划启动。请先取消计划,或立即开始后再${actionLabel}。`);
  3859. }
  3860. return state;
  3861. }
  3862. async function skipStep(step) {
  3863. const state = await ensureManualInteractionAllowed('跳过步骤');
  3864. if (!Number.isInteger(step) || !STEP_IDS.includes(step)) {
  3865. throw new Error(`无效步骤:${step}`);
  3866. }
  3867. const statuses = { ...(state.stepStatuses || {}) };
  3868. const currentStatus = statuses[step];
  3869. if (currentStatus === 'running') {
  3870. throw new Error(`步骤 ${step} 正在运行中,不能跳过。`);
  3871. }
  3872. if (isStepDoneStatus(currentStatus)) {
  3873. throw new Error(`步骤 ${step} 已完成,无需再跳过。`);
  3874. }
  3875. if (step > 1) {
  3876. const prevStatus = statuses[step - 1];
  3877. if (!isStepDoneStatus(prevStatus)) {
  3878. throw new Error(`请先完成步骤 ${step - 1},再跳过步骤 ${step}。`);
  3879. }
  3880. }
  3881. await setStepStatus(step, 'skipped');
  3882. await addLog(`步骤 ${step} 已跳过`, 'warn');
  3883. if (step === 1) {
  3884. const latestState = await getState();
  3885. const step2Status = latestState.stepStatuses?.[2];
  3886. if (!isStepDoneStatus(step2Status) && step2Status !== 'running') {
  3887. await setStepStatus(2, 'skipped');
  3888. await addLog('步骤 1 已跳过,步骤 2 也已同时跳过。', 'warn');
  3889. }
  3890. }
  3891. return { ok: true, step, status: 'skipped' };
  3892. }
  3893. function throwIfStopped() {
  3894. if (stopRequested) {
  3895. throw new Error(STOP_ERROR_MESSAGE);
  3896. }
  3897. }
  3898. async function sleepWithStop(ms) {
  3899. const start = Date.now();
  3900. while (Date.now() - start < ms) {
  3901. throwIfStopped();
  3902. await new Promise(r => setTimeout(r, Math.min(100, ms - (Date.now() - start))));
  3903. }
  3904. }
  3905. async function humanStepDelay(min = HUMAN_STEP_DELAY_MIN, max = HUMAN_STEP_DELAY_MAX) {
  3906. const duration = Math.floor(Math.random() * (max - min + 1)) + min;
  3907. await sleepWithStop(duration);
  3908. }
  3909. async function clickWithDebugger(tabId, rect) {
  3910. throwIfStopped();
  3911. if (!tabId) {
  3912. throw new Error('未找到用于调试点击的认证页面标签页。');
  3913. }
  3914. if (!rect || !Number.isFinite(rect.centerX) || !Number.isFinite(rect.centerY)) {
  3915. throw new Error('步骤 9 的调试器兜底点击需要有效的按钮坐标。');
  3916. }
  3917. const target = { tabId };
  3918. try {
  3919. await chrome.debugger.attach(target, '1.3');
  3920. } catch (err) {
  3921. throw new Error(
  3922. `步骤 9 的调试器兜底点击附加失败:${err.message}。` +
  3923. '如果认证页标签已打开 DevTools,请先关闭后重试。'
  3924. );
  3925. }
  3926. try {
  3927. throwIfStopped();
  3928. const x = Math.round(rect.centerX);
  3929. const y = Math.round(rect.centerY);
  3930. await chrome.debugger.sendCommand(target, 'Page.bringToFront');
  3931. throwIfStopped();
  3932. await chrome.debugger.sendCommand(target, 'Input.dispatchMouseEvent', {
  3933. type: 'mouseMoved',
  3934. x,
  3935. y,
  3936. button: 'none',
  3937. buttons: 0,
  3938. clickCount: 0,
  3939. });
  3940. throwIfStopped();
  3941. await chrome.debugger.sendCommand(target, 'Input.dispatchMouseEvent', {
  3942. type: 'mousePressed',
  3943. x,
  3944. y,
  3945. button: 'left',
  3946. buttons: 1,
  3947. clickCount: 1,
  3948. });
  3949. throwIfStopped();
  3950. await chrome.debugger.sendCommand(target, 'Input.dispatchMouseEvent', {
  3951. type: 'mouseReleased',
  3952. x,
  3953. y,
  3954. button: 'left',
  3955. buttons: 0,
  3956. clickCount: 1,
  3957. });
  3958. } finally {
  3959. await chrome.debugger.detach(target).catch(() => { });
  3960. }
  3961. }
  3962. async function broadcastStopToContentScripts() {
  3963. const registry = await getTabRegistry();
  3964. for (const entry of Object.values(registry)) {
  3965. if (!entry?.tabId) continue;
  3966. try {
  3967. await chrome.tabs.sendMessage(entry.tabId, {
  3968. type: 'STOP_FLOW',
  3969. source: 'background',
  3970. payload: {},
  3971. });
  3972. } catch { }
  3973. }
  3974. }
  3975. let stopRequested = false;
  3976. // ============================================================
  3977. // Message Handler (central router)
  3978. // ============================================================
  3979. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  3980. console.log(LOG_PREFIX, `Received: ${message.type} from ${message.source || 'sidepanel'}`, message);
  3981. handleMessage(message, sender).then(response => {
  3982. sendResponse(response);
  3983. }).catch(err => {
  3984. console.error(LOG_PREFIX, 'Handler error:', err);
  3985. sendResponse({ error: err.message });
  3986. });
  3987. return true; // async response
  3988. });
  3989. async function handleMessage(message, sender) {
  3990. return messageRouter.handleMessage(message, sender);
  3991. }
  3992. // ============================================================
  3993. // Step Data Handlers
  3994. // ============================================================
  3995. async function handleStepData(step, payload) {
  3996. if (typeof messageRouter !== 'undefined' && messageRouter?.handleStepData) {
  3997. return messageRouter.handleStepData(step, payload);
  3998. }
  3999. switch (step) {
  4000. case 1: {
  4001. const updates = {};
  4002. if (payload.oauthUrl) {
  4003. updates.oauthUrl = payload.oauthUrl;
  4004. broadcastDataUpdate({ oauthUrl: payload.oauthUrl });
  4005. }
  4006. if (payload.sub2apiSessionId !== undefined) updates.sub2apiSessionId = payload.sub2apiSessionId || null;
  4007. if (payload.sub2apiOAuthState !== undefined) updates.sub2apiOAuthState = payload.sub2apiOAuthState || null;
  4008. if (payload.sub2apiGroupId !== undefined) updates.sub2apiGroupId = payload.sub2apiGroupId || null;
  4009. if (payload.sub2apiDraftName !== undefined) updates.sub2apiDraftName = payload.sub2apiDraftName || null;
  4010. if (Object.keys(updates).length) {
  4011. await setState(updates);
  4012. }
  4013. break;
  4014. }
  4015. case 2:
  4016. if (payload.email) await setEmailState(payload.email);
  4017. if (payload.skippedPasswordStep) {
  4018. const latestState = await getState();
  4019. const step3Status = latestState.stepStatuses?.[3];
  4020. if (step3Status !== 'running' && step3Status !== 'completed' && step3Status !== 'manual_completed') {
  4021. await setStepStatus(3, 'skipped');
  4022. await addLog('步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。', 'warn');
  4023. }
  4024. }
  4025. break;
  4026. case 3:
  4027. if (payload.email) await setEmailState(payload.email);
  4028. if (payload.signupVerificationRequestedAt) {
  4029. await setState({ signupVerificationRequestedAt: payload.signupVerificationRequestedAt });
  4030. }
  4031. if (payload.loginVerificationRequestedAt) {
  4032. await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
  4033. }
  4034. break;
  4035. case 7:
  4036. if (payload.loginVerificationRequestedAt) {
  4037. await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
  4038. }
  4039. break;
  4040. case 4:
  4041. await setState({
  4042. lastEmailTimestamp: payload.emailTimestamp || null,
  4043. signupVerificationRequestedAt: null,
  4044. });
  4045. break;
  4046. case 8:
  4047. await setState({
  4048. lastEmailTimestamp: payload.emailTimestamp || null,
  4049. loginVerificationRequestedAt: null,
  4050. });
  4051. break;
  4052. case 9:
  4053. if (payload.localhostUrl) {
  4054. if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
  4055. throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。');
  4056. }
  4057. await setState({ localhostUrl: payload.localhostUrl });
  4058. broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
  4059. }
  4060. break;
  4061. case 10: {
  4062. if (payload.localhostUrl) {
  4063. await closeLocalhostCallbackTabs(payload.localhostUrl);
  4064. }
  4065. const latestState = await getState();
  4066. if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) {
  4067. await patchHotmailAccount(latestState.currentHotmailAccountId, {
  4068. used: true,
  4069. lastUsedAt: Date.now(),
  4070. });
  4071. await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
  4072. }
  4073. if (isLuckmailProvider(latestState)) {
  4074. const currentPurchase = getCurrentLuckmailPurchase(latestState);
  4075. if (currentPurchase?.id) {
  4076. await setLuckmailPurchaseUsedState(currentPurchase.id, true);
  4077. await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok');
  4078. }
  4079. await clearLuckmailRuntimeState({ clearEmail: true });
  4080. await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok');
  4081. }
  4082. const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
  4083. if (localhostPrefix) {
  4084. await closeTabsByUrlPrefix(localhostPrefix, {
  4085. excludeUrls: [payload.localhostUrl],
  4086. excludeLocalhostCallbacks: true,
  4087. });
  4088. }
  4089. await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
  4090. if (shouldUseCustomRegistrationEmail(latestState) && latestState.email) {
  4091. await setEmailStateSilently(null);
  4092. }
  4093. break;
  4094. }
  4095. }
  4096. }
  4097. // ============================================================
  4098. // Step Completion Waiting
  4099. // ============================================================
  4100. // Map of step -> { resolve, reject } for waiting on step completion
  4101. const stepWaiters = new Map();
  4102. let resumeWaiter = null;
  4103. const AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS = 120000;
  4104. const AUTO_RUN_BACKGROUND_COMPLETED_STEPS = new Set([1, 2, 4, 6, 7, 8, 9]);
  4105. const STEP_COMPLETION_SIGNAL_STEPS = new Set([3, 5, 10]);
  4106. function waitForStepComplete(step, timeoutMs = 120000) {
  4107. return new Promise((resolve, reject) => {
  4108. throwIfStopped();
  4109. if (stepWaiters.has(step)) {
  4110. console.warn(LOG_PREFIX, `[waitForStepComplete] replacing existing waiter for step ${step}`);
  4111. }
  4112. console.log(LOG_PREFIX, `[waitForStepComplete] register step ${step}, timeout=${timeoutMs}ms`);
  4113. const timer = setTimeout(() => {
  4114. stepWaiters.delete(step);
  4115. console.warn(LOG_PREFIX, `[waitForStepComplete] timeout for step ${step} after ${timeoutMs}ms`);
  4116. reject(new Error(`步骤 ${step} 等待超时(>${timeoutMs / 1000} 秒)`));
  4117. }, timeoutMs);
  4118. stepWaiters.set(step, {
  4119. resolve: (data) => { clearTimeout(timer); stepWaiters.delete(step); resolve(data); },
  4120. reject: (err) => { clearTimeout(timer); stepWaiters.delete(step); reject(err); },
  4121. });
  4122. });
  4123. }
  4124. function doesStepUseCompletionSignal(step) {
  4125. return STEP_COMPLETION_SIGNAL_STEPS.has(step);
  4126. }
  4127. function notifyStepComplete(step, payload) {
  4128. const waiter = stepWaiters.get(step);
  4129. console.log(LOG_PREFIX, `[notifyStepComplete] step ${step}, hasWaiter=${Boolean(waiter)}`);
  4130. if (waiter) waiter.resolve(payload);
  4131. }
  4132. function notifyStepError(step, error) {
  4133. const waiter = stepWaiters.get(step);
  4134. console.warn(LOG_PREFIX, `[notifyStepError] step ${step}, hasWaiter=${Boolean(waiter)}, error=${error}`);
  4135. if (waiter) waiter.reject(new Error(error));
  4136. }
  4137. async function completeStepFromBackground(step, payload = {}) {
  4138. if (stopRequested) {
  4139. await setStepStatus(step, 'stopped');
  4140. await appendManualAccountRunRecordIfNeeded(`step${step}_stopped`, null, STOP_ERROR_MESSAGE);
  4141. notifyStepError(step, STOP_ERROR_MESSAGE);
  4142. return;
  4143. }
  4144. const completionState = step === LAST_STEP_ID ? await getState() : null;
  4145. await setStepStatus(step, 'completed');
  4146. await addLog(`步骤 ${step} 已完成`, 'ok');
  4147. await handleStepData(step, payload);
  4148. if (step === LAST_STEP_ID) {
  4149. await appendAndBroadcastAccountRunRecord('success', completionState);
  4150. }
  4151. notifyStepComplete(step, payload);
  4152. }
  4153. async function appendManualAccountRunRecordIfNeeded(status, stateOverride = null, reason = '') {
  4154. if (!accountRunHistoryHelpers?.appendAccountRunRecord) {
  4155. return null;
  4156. }
  4157. const state = stateOverride || await getState();
  4158. if (isAutoRunLockedState(state)) {
  4159. return null;
  4160. }
  4161. return appendAndBroadcastAccountRunRecord(status, state, reason);
  4162. }
  4163. async function finalizeDeferredStepExecutionError(step, error) {
  4164. const latestState = await getState();
  4165. const currentStatus = latestState.stepStatuses?.[step];
  4166. if (currentStatus === 'completed' || currentStatus === 'failed' || currentStatus === 'stopped') {
  4167. return;
  4168. }
  4169. if (isStopError(error)) {
  4170. await setStepStatus(step, 'stopped');
  4171. await addLog(`步骤 ${step} 已被用户停止`, 'warn');
  4172. await appendManualAccountRunRecordIfNeeded(`step${step}_stopped`, latestState, getErrorMessage(error));
  4173. return;
  4174. }
  4175. await setStepStatus(step, 'failed');
  4176. await addLog(`步骤 ${step} 失败:${getErrorMessage(error)}`, 'error');
  4177. await appendManualAccountRunRecordIfNeeded(`step${step}_failed`, latestState, getErrorMessage(error));
  4178. }
  4179. async function executeStepViaCompletionSignal(step, timeoutMs = AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS) {
  4180. const completionResultPromise = waitForStepComplete(step, timeoutMs).then(
  4181. payload => ({ ok: true, payload }),
  4182. error => ({ ok: false, error }),
  4183. );
  4184. let executeError = null;
  4185. try {
  4186. await executeStep(step, { deferRetryableTransportError: true });
  4187. } catch (err) {
  4188. executeError = err;
  4189. if (isStopError(err) || !isRetryableContentScriptTransportError(err)) {
  4190. notifyStepError(step, getErrorMessage(err));
  4191. }
  4192. }
  4193. const completionResult = await completionResultPromise;
  4194. if (completionResult.ok) {
  4195. if (executeError) {
  4196. console.warn(
  4197. LOG_PREFIX,
  4198. `[executeStepViaCompletionSignal] step ${step} completed after deferred execute error: ${getErrorMessage(executeError)}`
  4199. );
  4200. }
  4201. return completionResult.payload;
  4202. }
  4203. if (executeError && isRetryableContentScriptTransportError(executeError)) {
  4204. const completionMessage = getErrorMessage(completionResult.error);
  4205. if (/等待超时/.test(completionMessage)) {
  4206. await finalizeDeferredStepExecutionError(step, executeError);
  4207. throw executeError;
  4208. }
  4209. throw completionResult.error;
  4210. }
  4211. if (executeError) {
  4212. throw executeError;
  4213. }
  4214. throw completionResult.error;
  4215. }
  4216. async function waitForRunningStepsToFinish(payload = {}) {
  4217. let currentState = await getState();
  4218. let runningSteps = getRunningSteps(currentState.stepStatuses);
  4219. if (!runningSteps.length) {
  4220. return currentState;
  4221. }
  4222. await addLog(`自动继续:检测到步骤 ${runningSteps.join(', ')} 正在运行,等待完成后再继续自动流程...`, 'info');
  4223. await broadcastAutoRunStatus('waiting_step', payload);
  4224. while (runningSteps.length) {
  4225. await sleepWithStop(250);
  4226. currentState = await getState();
  4227. runningSteps = getRunningSteps(currentState.stepStatuses);
  4228. }
  4229. await addLog('自动继续:当前运行步骤已结束,准备按最新进度继续自动流程...', 'info');
  4230. return currentState;
  4231. }
  4232. async function markRunningStepsStopped() {
  4233. const state = await getState();
  4234. const runningSteps = getRunningSteps(state.stepStatuses);
  4235. for (const step of runningSteps) {
  4236. await setStepStatus(step, 'stopped');
  4237. }
  4238. }
  4239. async function requestStop(options = {}) {
  4240. const { logMessage = '已收到停止请求,正在取消当前操作...' } = options;
  4241. const state = await getState();
  4242. const timerPlan = getPendingAutoRunTimerPlan(state);
  4243. if (timerPlan?.kind === AUTO_RUN_TIMER_KIND_SCHEDULED_START && !autoRunActive) {
  4244. await cancelScheduledAutoRun({
  4245. logMessage: options.logMessage === false
  4246. ? false
  4247. : (options.logMessage || '已取消自动运行倒计时计划。'),
  4248. });
  4249. return;
  4250. }
  4251. if (timerPlan && !autoRunActive) {
  4252. autoRunCurrentRun = timerPlan.currentRun;
  4253. autoRunTotalRuns = timerPlan.totalRuns;
  4254. autoRunAttemptRun = timerPlan.attemptRun;
  4255. if (options.logMessage !== false) {
  4256. await addLog(options.logMessage || '已停止等待中的自动流程。', 'warn');
  4257. }
  4258. await broadcastAutoRunStatus('stopped', {
  4259. currentRun: timerPlan.currentRun,
  4260. totalRuns: timerPlan.totalRuns,
  4261. attemptRun: timerPlan.attemptRun,
  4262. }, {
  4263. autoRunSkipFailures: timerPlan.autoRunSkipFailures,
  4264. autoRunRoundSummaries: serializeAutoRunRoundSummaries(timerPlan.totalRuns, timerPlan.roundSummaries),
  4265. autoRunTimerPlan: null,
  4266. scheduledAutoRunPlan: null,
  4267. });
  4268. await clearAutoRunTimerAlarm();
  4269. clearStopRequest();
  4270. return;
  4271. }
  4272. if (stopRequested) return;
  4273. stopRequested = true;
  4274. cancelPendingCommands();
  4275. cleanupStep8NavigationListeners();
  4276. rejectPendingStep8(new Error(STOP_ERROR_MESSAGE));
  4277. await addLog(logMessage, 'warn');
  4278. await broadcastStopToContentScripts();
  4279. for (const waiter of stepWaiters.values()) {
  4280. waiter.reject(new Error(STOP_ERROR_MESSAGE));
  4281. }
  4282. stepWaiters.clear();
  4283. if (resumeWaiter) {
  4284. resumeWaiter.reject(new Error(STOP_ERROR_MESSAGE));
  4285. resumeWaiter = null;
  4286. }
  4287. await markRunningStepsStopped();
  4288. autoRunActive = false;
  4289. await broadcastAutoRunStatus('stopped', {
  4290. currentRun: autoRunCurrentRun,
  4291. totalRuns: autoRunTotalRuns,
  4292. attemptRun: autoRunAttemptRun,
  4293. }, {
  4294. autoRunTimerPlan: null,
  4295. scheduledAutoRunPlan: null,
  4296. });
  4297. }
  4298. // ============================================================
  4299. // Step Execution
  4300. // ============================================================
  4301. async function executeStep(step, options = {}) {
  4302. const { deferRetryableTransportError = false } = options;
  4303. console.log(LOG_PREFIX, `Executing step ${step}`);
  4304. throwIfStopped();
  4305. await setStepStatus(step, 'running');
  4306. await addLog(`步骤 ${step} 开始执行`);
  4307. await humanStepDelay();
  4308. const state = await getState();
  4309. // Set flow start time on first step
  4310. if (step === 1 && !state.flowStartTime) {
  4311. await setState({ flowStartTime: Date.now() });
  4312. }
  4313. try {
  4314. await stepRegistry.executeStep(step, state);
  4315. } catch (err) {
  4316. if (isStopError(err)) {
  4317. await setStepStatus(step, 'stopped');
  4318. await addLog(`步骤 ${step} 已被用户停止`, 'warn');
  4319. await appendManualAccountRunRecordIfNeeded(`step${step}_stopped`, state, getErrorMessage(err));
  4320. throw err;
  4321. }
  4322. if (!(deferRetryableTransportError && doesStepUseCompletionSignal(step) && isRetryableContentScriptTransportError(err))) {
  4323. await setStepStatus(step, 'failed');
  4324. await addLog(`步骤 ${step} 失败:${err.message}`, 'error');
  4325. await appendManualAccountRunRecordIfNeeded(`step${step}_failed`, state, getErrorMessage(err));
  4326. } else {
  4327. console.warn(
  4328. LOG_PREFIX,
  4329. `[executeStep] deferring retryable transport error for step ${step}: ${getErrorMessage(err)}`
  4330. );
  4331. }
  4332. throw err;
  4333. }
  4334. }
  4335. /**
  4336. * Execute a step and wait for it to complete before returning.
  4337. * @param {number} step
  4338. * @param {number} delayAfter - ms to wait after completion (for page transitions)
  4339. */
  4340. async function executeStepAndWait(step, delayAfter = 2000) {
  4341. throwIfStopped();
  4342. const delaySeconds = normalizeAutoStepDelaySeconds((await getState()).autoStepDelaySeconds, null);
  4343. if (delaySeconds > 0) {
  4344. await addLog(
  4345. `自动运行:步骤 ${step} 执行前额外等待 ${delaySeconds} 秒,避免节奏过快。`,
  4346. 'info'
  4347. );
  4348. await sleepWithStop(delaySeconds * 1000);
  4349. }
  4350. if (AUTO_RUN_BACKGROUND_COMPLETED_STEPS.has(step)) {
  4351. await addLog(`自动运行:步骤 ${step} 由后台流程负责收尾,执行函数返回后将直接进入下一步。`, 'info');
  4352. await executeStep(step);
  4353. const latestState = await getState();
  4354. await addLog(`自动运行:步骤 ${step} 已执行返回,当前状态为 ${latestState.stepStatuses?.[step] || 'pending'},准备继续后续步骤。`, 'info');
  4355. } else if (doesStepUseCompletionSignal(step)) {
  4356. await addLog(`自动运行:步骤 ${step} 已发起,正在等待完成信号(超时 ${AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS / 1000} 秒)。`, 'info');
  4357. await executeStepViaCompletionSignal(step, AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS);
  4358. await addLog(`自动运行:步骤 ${step} 已收到完成信号,准备继续后续步骤。`, 'info');
  4359. } else {
  4360. await executeStep(step);
  4361. }
  4362. if (step === 5) {
  4363. const signupTabId = await getTabId('signup-page');
  4364. if (signupTabId) {
  4365. await addLog('自动运行:步骤 5 已收到完成信号,正在等待当前页面完成加载...', 'info');
  4366. await waitForTabComplete(signupTabId, {
  4367. timeoutMs: 15000,
  4368. retryDelayMs: 300,
  4369. });
  4370. }
  4371. }
  4372. // Extra delay for page transitions / DOM updates
  4373. if (delayAfter > 0) {
  4374. await sleepWithStop(delayAfter + Math.floor(Math.random() * 1200));
  4375. }
  4376. }
  4377. function getEmailGeneratorLabel(generator) {
  4378. if (generator === 'custom') {
  4379. return '自定义邮箱';
  4380. }
  4381. if (generator === 'icloud') {
  4382. return 'iCloud 隐私邮箱';
  4383. }
  4384. if (generator === 'cloudflare') return 'Cloudflare 邮箱';
  4385. if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return 'Cloudflare Temp Email';
  4386. return 'Duck 邮箱';
  4387. }
  4388. const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGeneratedEmailHelpers({
  4389. addLog,
  4390. buildGeneratedAliasEmail,
  4391. buildCloudflareTempEmailHeaders,
  4392. CLOUDFLARE_TEMP_EMAIL_GENERATOR,
  4393. DUCK_AUTOFILL_URL,
  4394. fetch,
  4395. fetchIcloudHideMyEmail,
  4396. getCloudflareTempEmailAddressFromResponse,
  4397. getCloudflareTempEmailConfig,
  4398. getState,
  4399. joinCloudflareTempEmailUrl,
  4400. normalizeCloudflareDomain,
  4401. normalizeCloudflareTempEmailAddress,
  4402. normalizeEmailGenerator,
  4403. isGeneratedAliasProvider,
  4404. reuseOrCreateTab,
  4405. sendToContentScript,
  4406. setEmailState,
  4407. throwIfStopped,
  4408. });
  4409. function generateCloudflareAliasLocalPart() {
  4410. return generatedEmailHelpers.generateCloudflareAliasLocalPart();
  4411. }
  4412. async function fetchCloudflareEmail(state, options = {}) {
  4413. return generatedEmailHelpers.fetchCloudflareEmail(state, options);
  4414. }
  4415. function ensureCloudflareTempEmailConfig(state, options = {}) {
  4416. return generatedEmailHelpers.ensureCloudflareTempEmailConfig(state, options);
  4417. }
  4418. async function requestCloudflareTempEmailJson(config, path, options = {}) {
  4419. return generatedEmailHelpers.requestCloudflareTempEmailJson(config, path, options);
  4420. }
  4421. async function fetchCloudflareTempEmailAddress(state, options = {}) {
  4422. return generatedEmailHelpers.fetchCloudflareTempEmailAddress(state, options);
  4423. }
  4424. async function fetchDuckEmail(options = {}) {
  4425. return generatedEmailHelpers.fetchDuckEmail(options);
  4426. }
  4427. async function fetchGeneratedEmail(state, options = {}) {
  4428. return generatedEmailHelpers.fetchGeneratedEmail(state, options);
  4429. }
  4430. // ============================================================
  4431. // Auto Run Flow
  4432. // ============================================================
  4433. let autoRunActive = false;
  4434. let autoRunCurrentRun = 0;
  4435. let autoRunTotalRuns = 1;
  4436. let autoRunAttemptRun = 0;
  4437. const EMAIL_FETCH_MAX_ATTEMPTS = 5;
  4438. const VERIFICATION_POLL_MAX_ROUNDS = 5;
  4439. const STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS = 25000;
  4440. const MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15;
  4441. const MAIL_2925_VERIFICATION_INTERVAL_MS = 15000;
  4442. const AUTO_STEP_DELAYS = {
  4443. 1: 2000,
  4444. 2: 2000,
  4445. 3: 3000,
  4446. 4: 2000,
  4447. 5: 0,
  4448. 6: 3000,
  4449. 7: 2000,
  4450. 8: 2000,
  4451. 9: 1000,
  4452. };
  4453. const accountRunHistoryHelpers = self.MultiPageBackgroundAccountRunHistory?.createAccountRunHistoryHelpers({
  4454. ACCOUNT_RUN_HISTORY_STORAGE_KEY,
  4455. addLog,
  4456. buildLocalHelperEndpoint: (baseUrl, path) => buildHotmailLocalEndpoint(baseUrl, path),
  4457. chrome,
  4458. getErrorMessage,
  4459. getState,
  4460. normalizeAccountRunHistoryHelperBaseUrl,
  4461. });
  4462. async function broadcastAccountRunHistoryUpdate() {
  4463. if (!accountRunHistoryHelpers?.getPersistedAccountRunHistory) {
  4464. return [];
  4465. }
  4466. const history = await accountRunHistoryHelpers.getPersistedAccountRunHistory();
  4467. broadcastDataUpdate({ accountRunHistory: history });
  4468. return history;
  4469. }
  4470. async function appendAndBroadcastAccountRunRecord(status, stateOverride = null, reason = '') {
  4471. if (!accountRunHistoryHelpers?.appendAccountRunRecord) {
  4472. return null;
  4473. }
  4474. const record = await accountRunHistoryHelpers.appendAccountRunRecord(status, stateOverride, reason);
  4475. if (!record) {
  4476. return null;
  4477. }
  4478. await broadcastAccountRunHistoryUpdate();
  4479. return record;
  4480. }
  4481. const autoRunController = self.MultiPageBackgroundAutoRunController?.createAutoRunController({
  4482. addLog,
  4483. appendAccountRunRecord: (...args) => appendAndBroadcastAccountRunRecord(...args),
  4484. AUTO_RUN_MAX_RETRIES_PER_ROUND,
  4485. AUTO_RUN_RETRY_DELAY_MS,
  4486. AUTO_RUN_TIMER_KIND_BEFORE_RETRY,
  4487. AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS,
  4488. broadcastAutoRunStatus,
  4489. broadcastStopToContentScripts,
  4490. cancelPendingCommands,
  4491. clearStopRequest: () => clearStopRequest(),
  4492. getAutoRunStatusPayload,
  4493. getErrorMessage,
  4494. getFirstUnfinishedStep,
  4495. getPendingAutoRunTimerPlan,
  4496. getRunningSteps,
  4497. getState,
  4498. getStopRequested: () => stopRequested,
  4499. hasSavedProgress,
  4500. isRestartCurrentAttemptError,
  4501. isStopError,
  4502. launchAutoRunTimerPlan,
  4503. normalizeAutoRunFallbackThreadIntervalMinutes,
  4504. persistAutoRunTimerPlan,
  4505. resetState,
  4506. runAutoSequenceFromStep: (...args) => runAutoSequenceFromStep(...args),
  4507. runtime: {
  4508. get: () => ({
  4509. autoRunActive,
  4510. autoRunCurrentRun,
  4511. autoRunTotalRuns,
  4512. autoRunAttemptRun,
  4513. }),
  4514. set: (updates = {}) => {
  4515. if (updates.autoRunActive !== undefined) autoRunActive = Boolean(updates.autoRunActive);
  4516. if (updates.autoRunCurrentRun !== undefined) autoRunCurrentRun = Number(updates.autoRunCurrentRun) || 0;
  4517. if (updates.autoRunTotalRuns !== undefined) autoRunTotalRuns = Number(updates.autoRunTotalRuns) || 0;
  4518. if (updates.autoRunAttemptRun !== undefined) autoRunAttemptRun = Number(updates.autoRunAttemptRun) || 0;
  4519. },
  4520. },
  4521. setState,
  4522. sleepWithStop,
  4523. waitForRunningStepsToFinish,
  4524. throwIfStopped: () => throwIfStopped(),
  4525. chrome,
  4526. });
  4527. async function resumeAutoRunIfWaitingForEmail(options = {}) {
  4528. const { silent = false } = options;
  4529. const state = await getState();
  4530. if (!state.email || !isAutoRunPausedState(state)) {
  4531. return false;
  4532. }
  4533. if (resumeWaiter) {
  4534. if (!silent) {
  4535. await addLog('邮箱已就绪,自动继续后续步骤...', 'info');
  4536. }
  4537. resumeWaiter.resolve();
  4538. resumeWaiter = null;
  4539. return true;
  4540. }
  4541. return false;
  4542. }
  4543. async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
  4544. const currentState = await getState();
  4545. if (isHotmailProvider(currentState)) {
  4546. const account = await ensureHotmailAccountForFlow({
  4547. allowAllocate: true,
  4548. markUsed: true,
  4549. preferredAccountId: null,
  4550. });
  4551. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:已分配 Hotmail 账号 ${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok');
  4552. return account.email;
  4553. }
  4554. if (isLuckmailProvider(currentState)) {
  4555. const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
  4556. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:LuckMail 邮箱已就绪:${purchase.email_address}(第 ${attemptRuns} 次尝试)===`, 'ok');
  4557. return purchase.email_address;
  4558. }
  4559. if (isGeneratedAliasProvider(currentState)) {
  4560. if (currentState.mailProvider === GMAIL_PROVIDER) {
  4561. if (!currentState.emailPrefix) {
  4562. throw new Error('Gmail 原邮箱未设置,请先在侧边栏填写。');
  4563. }
  4564. await addLog(`=== 鐩爣 ${targetRun}/${totalRuns} 杞細Gmail +tag 妯″紡宸插惎鐢紝灏嗗湪姝ラ 3 鑷姩鐢熸垚閭锛堢 ${attemptRuns} 娆″皾璇曪級===`, 'info');
  4565. return null;
  4566. }
  4567. if (!currentState.emailPrefix) {
  4568. throw new Error('2925 邮箱前缀未设置,请先在侧边栏填写。');
  4569. }
  4570. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:2925 模式已启用,将在步骤 3 自动生成邮箱(第 ${attemptRuns} 次尝试)===`, 'info');
  4571. return null;
  4572. }
  4573. if (currentState.email) {
  4574. return currentState.email;
  4575. }
  4576. if (shouldUseCustomRegistrationEmail(currentState)) {
  4577. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
  4578. await broadcastAutoRunStatus('waiting_email', {
  4579. currentRun: targetRun,
  4580. totalRuns,
  4581. attemptRun: attemptRuns,
  4582. });
  4583. await waitForResume();
  4584. const resumedState = await getState();
  4585. if (!resumedState.email) {
  4586. throw new Error('无法继续:当前没有注册邮箱。');
  4587. }
  4588. return resumedState.email;
  4589. }
  4590. const generator = normalizeEmailGenerator(currentState.emailGenerator);
  4591. const generatorLabel = getEmailGeneratorLabel(generator);
  4592. let lastError = null;
  4593. for (let attempt = 1; attempt <= EMAIL_FETCH_MAX_ATTEMPTS; attempt++) {
  4594. try {
  4595. if (attempt > 1) {
  4596. await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
  4597. }
  4598. const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator });
  4599. await addLog(
  4600. `=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
  4601. 'ok'
  4602. );
  4603. return generatedEmail;
  4604. } catch (err) {
  4605. lastError = err;
  4606. await addLog(`${generatorLabel}自动获取失败(${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS}):${err.message}`, 'warn');
  4607. if (
  4608. (generator === 'cloudflare' && /域名/.test(String(err.message || '')))
  4609. || (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR && /(服务地址|Admin Auth|域名)/.test(String(err.message || '')))
  4610. ) {
  4611. break;
  4612. }
  4613. }
  4614. }
  4615. await addLog(`${generatorLabel}自动获取已连续失败 ${EMAIL_FETCH_MAX_ATTEMPTS} 次:${lastError?.message || '未知错误'}`, 'error');
  4616. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先自动获取邮箱或手动粘贴邮箱,然后继续 ===`, 'warn');
  4617. await broadcastAutoRunStatus('waiting_email', {
  4618. currentRun: targetRun,
  4619. totalRuns,
  4620. attemptRun: attemptRuns,
  4621. });
  4622. await waitForResume();
  4623. const resumedState = await getState();
  4624. if (!resumedState.email) {
  4625. throw new Error('无法继续:当前没有邮箱地址。');
  4626. }
  4627. return resumedState.email;
  4628. }
  4629. async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
  4630. const currentState = await getState();
  4631. if (isHotmailProvider(currentState)) {
  4632. const account = await ensureHotmailAccountForFlow({
  4633. allowAllocate: true,
  4634. markUsed: true,
  4635. preferredAccountId: null,
  4636. });
  4637. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:已分配 Hotmail 账号 ${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok');
  4638. return account.email;
  4639. }
  4640. if (isLuckmailProvider(currentState)) {
  4641. const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
  4642. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:LuckMail 邮箱已就绪:${purchase.email_address}(第 ${attemptRuns} 次尝试)===`, 'ok');
  4643. return purchase.email_address;
  4644. }
  4645. if (isGeneratedAliasProvider(currentState)) {
  4646. if (isReusableGeneratedAliasEmail(currentState)) {
  4647. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:当前已复用 ${currentState.email},将直接继续执行(第 ${attemptRuns} 次尝试)===`, 'info');
  4648. return currentState.email;
  4649. }
  4650. const baseEmail = getManagedAliasBaseEmail(currentState);
  4651. if (!baseEmail && !currentState.email) {
  4652. const baseLabel = currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱';
  4653. throw new Error(`${baseLabel}未设置,请先填写,或直接在“注册邮箱”中手动填写完整邮箱。`);
  4654. }
  4655. await addLog(
  4656. `=== 目标 ${targetRun}/${totalRuns} 轮:${currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail +tag' : '2925'} 模式已启用,将在步骤 3 自动生成邮箱(第 ${attemptRuns} 次尝试)===`,
  4657. 'info'
  4658. );
  4659. return null;
  4660. }
  4661. if (currentState.email) {
  4662. return currentState.email;
  4663. }
  4664. if (shouldUseCustomRegistrationEmail(currentState)) {
  4665. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
  4666. await broadcastAutoRunStatus('waiting_email', {
  4667. currentRun: targetRun,
  4668. totalRuns,
  4669. attemptRun: attemptRuns,
  4670. });
  4671. await waitForResume();
  4672. const resumedState = await getState();
  4673. if (!resumedState.email) {
  4674. throw new Error('无法继续:当前没有注册邮箱。');
  4675. }
  4676. return resumedState.email;
  4677. }
  4678. const generator = normalizeEmailGenerator(currentState.emailGenerator);
  4679. const generatorLabel = getEmailGeneratorLabel(generator);
  4680. let lastError = null;
  4681. for (let attempt = 1; attempt <= EMAIL_FETCH_MAX_ATTEMPTS; attempt++) {
  4682. try {
  4683. if (attempt > 1) {
  4684. await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
  4685. }
  4686. const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator });
  4687. await addLog(
  4688. `=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
  4689. 'ok'
  4690. );
  4691. return generatedEmail;
  4692. } catch (err) {
  4693. lastError = err;
  4694. await addLog(`${generatorLabel}自动获取失败(${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS}):${err.message}`, 'warn');
  4695. if (
  4696. (generator === 'cloudflare' && /域名/.test(String(err.message || '')))
  4697. || (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR && /(服务地址|Admin Auth|域名)/.test(String(err.message || '')))
  4698. ) {
  4699. break;
  4700. }
  4701. }
  4702. }
  4703. await addLog(`${generatorLabel}自动获取已连续失败 ${EMAIL_FETCH_MAX_ATTEMPTS} 次:${lastError?.message || '未知错误'}`, 'error');
  4704. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先自动获取邮箱或手动粘贴邮箱,然后继续 ===`, 'warn');
  4705. await broadcastAutoRunStatus('waiting_email', {
  4706. currentRun: targetRun,
  4707. totalRuns,
  4708. attemptRun: attemptRuns,
  4709. });
  4710. await waitForResume();
  4711. const resumedState = await getState();
  4712. if (!resumedState.email) {
  4713. throw new Error('无法继续:当前没有邮箱地址。');
  4714. }
  4715. return resumedState.email;
  4716. }
  4717. async function runAutoSequenceFromStep(startStep, context = {}) {
  4718. const { targetRun, totalRuns, attemptRuns, continued = false } = context;
  4719. let postStep7RestartCount = 0;
  4720. let step4RestartCount = 0;
  4721. let currentStartStep = startStep;
  4722. let continueCurrentAttempt = continued;
  4723. while (true) {
  4724. if (continueCurrentAttempt) {
  4725. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续当前进度,从步骤 ${startStep} 开始(第 ${attemptRuns} 次尝试)===`, 'info');
  4726. } else {
  4727. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:第 ${attemptRuns} 次尝试,阶段 1,打开官网并进入密码页 ===`, 'info');
  4728. }
  4729. if (currentStartStep <= 1) {
  4730. await executeStepAndWait(1, AUTO_STEP_DELAYS[1]);
  4731. }
  4732. if (currentStartStep <= 2) {
  4733. await ensureAutoEmailReady(targetRun, totalRuns, attemptRuns);
  4734. await executeStepAndWait(2, AUTO_STEP_DELAYS[2]);
  4735. }
  4736. if (currentStartStep <= 3) {
  4737. const latestState = await getState();
  4738. const step3Status = latestState.stepStatuses?.[3] || 'pending';
  4739. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:阶段 2,填写密码、验证、登录并完成授权(第 ${attemptRuns} 次尝试)===`, 'info');
  4740. await broadcastAutoRunStatus('running', {
  4741. currentRun: targetRun,
  4742. totalRuns,
  4743. attemptRun: attemptRuns,
  4744. });
  4745. if (isStepDoneStatus(step3Status)) {
  4746. await addLog(`自动运行:步骤 3 当前状态为 ${step3Status},将直接继续后续流程。`, 'info');
  4747. } else {
  4748. await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
  4749. }
  4750. } else {
  4751. await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续执行剩余流程(第 ${attemptRuns} 次尝试)===`, 'info');
  4752. }
  4753. const signupTabId = await getTabId('signup-page');
  4754. if (signupTabId) {
  4755. await chrome.tabs.update(signupTabId, { active: true });
  4756. }
  4757. let restartFromStep1WithCurrentEmail = false;
  4758. let step = Math.max(currentStartStep, 4);
  4759. while (step <= LAST_STEP_ID) {
  4760. try {
  4761. await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
  4762. const latestState = await getState();
  4763. if (step === FINAL_OAUTH_CHAIN_START_STEP && shouldSkipLoginVerificationForCpaCallback(latestState)) {
  4764. step = 9;
  4765. continue;
  4766. }
  4767. step += 1;
  4768. } catch (err) {
  4769. if (isStopError(err)) {
  4770. throw err;
  4771. }
  4772. if (step === 4) {
  4773. step4RestartCount += 1;
  4774. const preservedState = await getState();
  4775. const preservedEmail = String(preservedState.email || '').trim();
  4776. const preservedPassword = String(preservedState.password || '').trim();
  4777. const emailSuffix = preservedEmail ? `当前邮箱:${preservedEmail};` : '';
  4778. await addLog(
  4779. `步骤 4:执行失败,准备沿用当前邮箱回到步骤 1 重新开始(第 ${step4RestartCount} 次重开)。${emailSuffix}原因:${getErrorMessage(err)}`,
  4780. 'warn'
  4781. );
  4782. await invalidateDownstreamAfterStepRestart(1, {
  4783. logLabel: `步骤 4 报错后准备回到步骤 1 沿用当前邮箱重试(第 ${step4RestartCount} 次重开)`,
  4784. });
  4785. const restorePayload = {};
  4786. if (preservedEmail) restorePayload.email = preservedEmail;
  4787. if (preservedPassword) restorePayload.password = preservedPassword;
  4788. if (Object.keys(restorePayload).length) {
  4789. await setState(restorePayload);
  4790. }
  4791. currentStartStep = 1;
  4792. continueCurrentAttempt = true;
  4793. restartFromStep1WithCurrentEmail = true;
  4794. break;
  4795. }
  4796. const restartDecision = await getPostStep6AutoRestartDecision(step, err);
  4797. if (restartDecision.shouldRestart) {
  4798. postStep7RestartCount += 1;
  4799. const authState = restartDecision.authState;
  4800. const authStateLabel = authState?.state ? getLoginAuthStateLabel(authState.state) : '未知页面';
  4801. const authStateSuffix = authState?.url
  4802. ? `当前认证页:${authStateLabel}(${authState.url})`
  4803. : authState?.state
  4804. ? `当前认证页:${authStateLabel}`
  4805. : '未获取到认证页状态';
  4806. await addLog(
  4807. `步骤 ${step}:检测到报错且当前未进入 add-phone,正在回到步骤 7 重新开始授权流程(第 ${postStep7RestartCount} 次重开)。${authStateSuffix};原因:${restartDecision.errorMessage || '未知错误'}`,
  4808. 'warn'
  4809. );
  4810. await invalidateDownstreamAfterStepRestart(6, {
  4811. logLabel: `步骤 ${step} 报错后准备回到步骤 7 重试(第 ${postStep7RestartCount} 次重开)`,
  4812. });
  4813. step = 7;
  4814. continue;
  4815. }
  4816. if (restartDecision.blockedByAddPhone) {
  4817. const addPhoneUrl = restartDecision.authState?.url || 'https://auth.openai.com/add-phone';
  4818. await addLog(`步骤 ${step}:检测到认证流程进入 add-phone(${addPhoneUrl}),停止自动回到步骤 7 重开。`, 'warn');
  4819. }
  4820. throw err;
  4821. }
  4822. }
  4823. if (restartFromStep1WithCurrentEmail) {
  4824. continue;
  4825. }
  4826. break;
  4827. }
  4828. }
  4829. async function waitForResume() {
  4830. throwIfStopped();
  4831. const state = await getState();
  4832. if (state.email) {
  4833. await addLog('邮箱已就绪,自动继续后续步骤...', 'info');
  4834. return;
  4835. }
  4836. return new Promise((resolve, reject) => {
  4837. resumeWaiter = { resolve, reject };
  4838. });
  4839. }
  4840. function createAutoRunRoundSummary(round) {
  4841. return autoRunController.createAutoRunRoundSummary(round);
  4842. }
  4843. function normalizeAutoRunRoundSummary(summary, round) {
  4844. return autoRunController.normalizeAutoRunRoundSummary(summary, round);
  4845. }
  4846. function buildAutoRunRoundSummaries(totalRuns, rawSummaries = []) {
  4847. return autoRunController.buildAutoRunRoundSummaries(totalRuns, rawSummaries);
  4848. }
  4849. function serializeAutoRunRoundSummaries(totalRuns, roundSummaries = []) {
  4850. return autoRunController.serializeAutoRunRoundSummaries(totalRuns, roundSummaries);
  4851. }
  4852. function getAutoRunRoundRetryCount(summary) {
  4853. return autoRunController.getAutoRunRoundRetryCount(summary);
  4854. }
  4855. function formatAutoRunFailureReasons(reasons = []) {
  4856. return autoRunController.formatAutoRunFailureReasons(reasons);
  4857. }
  4858. async function logAutoRunFinalSummary(totalRuns, roundSummaries = []) {
  4859. return autoRunController.logAutoRunFinalSummary(totalRuns, roundSummaries);
  4860. }
  4861. async function skipAutoRunCountdown() {
  4862. return autoRunController.skipAutoRunCountdown();
  4863. }
  4864. async function waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary, options = {}) {
  4865. return autoRunController.waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary, options);
  4866. }
  4867. async function waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun, options = {}) {
  4868. return autoRunController.waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun, options);
  4869. }
  4870. async function handleAutoRunLoopUnhandledError(error) {
  4871. return autoRunController.handleAutoRunLoopUnhandledError(error);
  4872. }
  4873. function startAutoRunLoop(totalRuns, options = {}) {
  4874. return autoRunController.startAutoRunLoop(totalRuns, options);
  4875. }
  4876. async function autoRunLoop(totalRuns, options = {}) {
  4877. return autoRunController.autoRunLoop(totalRuns, options);
  4878. }
  4879. async function resumeAutoRun() {
  4880. throwIfStopped();
  4881. const state = await getState();
  4882. if (!state.email) {
  4883. await addLog('无法继续:当前没有邮箱地址,请先在侧边栏填写邮箱。', 'error');
  4884. return false;
  4885. }
  4886. const resumedInMemory = await resumeAutoRunIfWaitingForEmail({ silent: true });
  4887. if (resumedInMemory) {
  4888. return true;
  4889. }
  4890. if (!isAutoRunPausedState(state)) {
  4891. return false;
  4892. }
  4893. if (autoRunActive) {
  4894. return false;
  4895. }
  4896. const totalRuns = state.autoRunTotalRuns || 1;
  4897. const currentRun = state.autoRunCurrentRun || 1;
  4898. const attemptRun = state.autoRunAttemptRun || 1;
  4899. await addLog('检测到自动流程暂停上下文已丢失,正在从当前进度恢复自动运行...', 'warn');
  4900. startAutoRunLoop(totalRuns, {
  4901. autoRunSkipFailures: Boolean(state.autoRunSkipFailures),
  4902. mode: 'continue',
  4903. resumeCurrentRun: currentRun,
  4904. resumeAttemptRun: attemptRun,
  4905. resumeRoundSummaries: state.autoRunRoundSummaries,
  4906. });
  4907. return true;
  4908. }
  4909. // ============================================================
  4910. // Signup / OAuth Helpers
  4911. // ============================================================
  4912. const SIGNUP_ENTRY_URL = 'https://chatgpt.com/';
  4913. const SIGNUP_PAGE_INJECT_FILES = ['content/utils.js', 'content/auth-page-recovery.js', 'content/signup-page.js'];
  4914. const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({
  4915. chrome,
  4916. addLog,
  4917. closeConflictingTabsForSource,
  4918. ensureContentScriptReadyOnTab,
  4919. getPanelMode,
  4920. normalizeSub2ApiUrl,
  4921. rememberSourceLastUrl,
  4922. sendToContentScript,
  4923. sendToContentScriptResilient,
  4924. waitForTabUrlFamily,
  4925. DEFAULT_SUB2API_GROUP_NAME,
  4926. SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
  4927. });
  4928. const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpers({
  4929. addLog,
  4930. buildGeneratedAliasEmail,
  4931. chrome,
  4932. ensureContentScriptReadyOnTab,
  4933. ensureHotmailAccountForFlow,
  4934. ensureLuckmailPurchaseForFlow,
  4935. getTabId,
  4936. isGeneratedAliasProvider,
  4937. isReusableGeneratedAliasEmail,
  4938. isSignupEmailVerificationPageUrl,
  4939. isHotmailProvider,
  4940. isLuckmailProvider,
  4941. isSignupPasswordPageUrl,
  4942. isTabAlive,
  4943. reuseOrCreateTab,
  4944. sendToContentScriptResilient,
  4945. setEmailState,
  4946. SIGNUP_ENTRY_URL,
  4947. SIGNUP_PAGE_INJECT_FILES,
  4948. waitForTabUrlMatch,
  4949. });
  4950. const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.createVerificationFlowHelpers({
  4951. addLog,
  4952. chrome,
  4953. CLOUDFLARE_TEMP_EMAIL_PROVIDER,
  4954. completeStepFromBackground,
  4955. confirmCustomVerificationStepBypassRequest: (step) => chrome.runtime.sendMessage({
  4956. type: 'REQUEST_CUSTOM_VERIFICATION_BYPASS_CONFIRMATION',
  4957. payload: { step },
  4958. }),
  4959. getHotmailVerificationPollConfig,
  4960. getHotmailVerificationRequestTimestamp,
  4961. getState,
  4962. getTabId,
  4963. HOTMAIL_PROVIDER,
  4964. isStopError,
  4965. LUCKMAIL_PROVIDER,
  4966. MAIL_2925_VERIFICATION_INTERVAL_MS,
  4967. MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
  4968. pollCloudflareTempEmailVerificationCode,
  4969. pollHotmailVerificationCode,
  4970. pollLuckmailVerificationCode,
  4971. sendToContentScript,
  4972. sendToMailContentScriptResilient,
  4973. setState,
  4974. setStepStatus,
  4975. sleepWithStop,
  4976. throwIfStopped,
  4977. VERIFICATION_POLL_MAX_ROUNDS,
  4978. });
  4979. const step1Executor = self.MultiPageBackgroundStep1?.createStep1Executor({
  4980. addLog,
  4981. completeStepFromBackground,
  4982. ensureSignupEntryPageReady,
  4983. });
  4984. const step2Executor = self.MultiPageBackgroundStep2?.createStep2Executor({
  4985. addLog,
  4986. chrome,
  4987. completeStepFromBackground,
  4988. ensureContentScriptReadyOnTab,
  4989. ensureSignupEntryPageReady,
  4990. ensureSignupPostEmailPageReadyInTab,
  4991. getTabId,
  4992. isTabAlive,
  4993. resolveSignupEmailForFlow,
  4994. sendToContentScriptResilient,
  4995. SIGNUP_PAGE_INJECT_FILES,
  4996. });
  4997. const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({
  4998. addLog,
  4999. chrome,
  5000. ensureContentScriptReadyOnTab,
  5001. generatePassword,
  5002. getTabId,
  5003. isTabAlive,
  5004. sendToContentScript,
  5005. setPasswordState,
  5006. setState,
  5007. SIGNUP_PAGE_INJECT_FILES,
  5008. });
  5009. const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({
  5010. addLog,
  5011. chrome,
  5012. completeStepFromBackground,
  5013. confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
  5014. getMailConfig,
  5015. getTabId,
  5016. HOTMAIL_PROVIDER,
  5017. isTabAlive,
  5018. LUCKMAIL_PROVIDER,
  5019. CLOUDFLARE_TEMP_EMAIL_PROVIDER,
  5020. resolveVerificationStep: verificationFlowHelpers.resolveVerificationStep,
  5021. reuseOrCreateTab,
  5022. sendToContentScriptResilient,
  5023. shouldUseCustomRegistrationEmail,
  5024. STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
  5025. throwIfStopped,
  5026. });
  5027. const step5Executor = self.MultiPageBackgroundStep5?.createStep5Executor({
  5028. addLog,
  5029. generateRandomBirthday,
  5030. generateRandomName,
  5031. sendToContentScript,
  5032. });
  5033. const step6Executor = self.MultiPageBackgroundStep6?.createStep6Executor({
  5034. completeStepFromBackground,
  5035. runPreStep6CookieCleanup,
  5036. });
  5037. const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({
  5038. addLog,
  5039. completeStepFromBackground,
  5040. getErrorMessage,
  5041. getLoginAuthStateLabel,
  5042. getState,
  5043. isStep6RecoverableResult,
  5044. isStep6SuccessResult,
  5045. refreshOAuthUrlBeforeStep6,
  5046. reuseOrCreateTab,
  5047. sendToContentScriptResilient,
  5048. shouldSkipLoginVerificationForCpaCallback,
  5049. skipLoginVerificationStepsForCpaCallback,
  5050. STEP6_MAX_ATTEMPTS,
  5051. throwIfStopped,
  5052. });
  5053. const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
  5054. addLog,
  5055. chrome,
  5056. CLOUDFLARE_TEMP_EMAIL_PROVIDER,
  5057. confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
  5058. ensureStep8VerificationPageReady,
  5059. executeStep7: (...args) => executeStep7(...args),
  5060. getPanelMode,
  5061. getMailConfig,
  5062. getState,
  5063. getTabId,
  5064. HOTMAIL_PROVIDER,
  5065. isTabAlive,
  5066. isVerificationMailPollingError,
  5067. LUCKMAIL_PROVIDER,
  5068. resolveVerificationStep: verificationFlowHelpers.resolveVerificationStep,
  5069. reuseOrCreateTab,
  5070. setState,
  5071. setStepStatus,
  5072. shouldSkipLoginVerificationForCpaCallback,
  5073. shouldUseCustomRegistrationEmail,
  5074. sleepWithStop,
  5075. STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
  5076. STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS,
  5077. throwIfStopped,
  5078. });
  5079. const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
  5080. addLog,
  5081. chrome,
  5082. closeConflictingTabsForSource,
  5083. completeStepFromBackground,
  5084. ensureContentScriptReadyOnTab,
  5085. getPanelMode,
  5086. getTabId,
  5087. isLocalhostOAuthCallbackUrl,
  5088. isTabAlive,
  5089. normalizeSub2ApiUrl,
  5090. rememberSourceLastUrl,
  5091. reuseOrCreateTab,
  5092. sendToContentScript,
  5093. sendToContentScriptResilient,
  5094. shouldBypassStep9ForLocalCpa,
  5095. SUB2API_STEP9_RESPONSE_TIMEOUT_MS,
  5096. });
  5097. const stepDefinitions = SHARED_STEP_DEFINITIONS;
  5098. const stepExecutorsByKey = {
  5099. 'open-chatgpt': () => step1Executor.executeStep1(),
  5100. 'submit-signup-email': (state) => step2Executor.executeStep2(state),
  5101. 'fill-password': (state) => step3Executor.executeStep3(state),
  5102. 'fetch-signup-code': (state) => step4Executor.executeStep4(state),
  5103. 'fill-profile': (state) => step5Executor.executeStep5(state),
  5104. 'clear-login-cookies': () => step6Executor.executeStep6(),
  5105. 'oauth-login': (state) => step7Executor.executeStep7(state),
  5106. 'fetch-login-code': (state) => step8Executor.executeStep8(state),
  5107. 'confirm-oauth': (state) => step9Executor.executeStep9(state),
  5108. 'platform-verify': (state) => step10Executor.executeStep10(state),
  5109. };
  5110. const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter({
  5111. addLog,
  5112. appendAccountRunRecord: (...args) => appendAndBroadcastAccountRunRecord(...args),
  5113. batchUpdateLuckmailPurchases,
  5114. buildLocalhostCleanupPrefix,
  5115. buildLuckmailSessionSettingsPayload,
  5116. buildPersistentSettingsPayload,
  5117. broadcastDataUpdate,
  5118. cancelScheduledAutoRun,
  5119. checkIcloudSession,
  5120. clearAutoRunTimerAlarm,
  5121. clearLuckmailRuntimeState,
  5122. clearStopRequest,
  5123. closeLocalhostCallbackTabs,
  5124. closeTabsByUrlPrefix,
  5125. deleteHotmailAccount,
  5126. deleteHotmailAccounts,
  5127. deleteIcloudAlias,
  5128. deleteUsedIcloudAliases,
  5129. disableUsedLuckmailPurchases,
  5130. doesStepUseCompletionSignal,
  5131. ensureManualInteractionAllowed,
  5132. executeStep,
  5133. executeStepViaCompletionSignal,
  5134. exportSettingsBundle,
  5135. fetchGeneratedEmail,
  5136. finalizeStep3Completion: async () => {
  5137. const currentState = await getState();
  5138. const signupTabId = await getTabId('signup-page');
  5139. return signupFlowHelpers.finalizeSignupPasswordSubmitInTab(
  5140. signupTabId,
  5141. currentState.password || currentState.customPassword || '',
  5142. 3
  5143. );
  5144. },
  5145. finalizeIcloudAliasAfterSuccessfulFlow,
  5146. findHotmailAccount,
  5147. flushCommand,
  5148. getCurrentLuckmailPurchase,
  5149. getPendingAutoRunTimerPlan,
  5150. getSourceLabel,
  5151. getState,
  5152. getStopRequested: () => stopRequested,
  5153. handleAutoRunLoopUnhandledError,
  5154. importSettingsBundle,
  5155. invalidateDownstreamAfterStepRestart,
  5156. isAutoRunLockedState,
  5157. isHotmailProvider,
  5158. isLocalhostOAuthCallbackUrl,
  5159. isLuckmailProvider,
  5160. isStopError,
  5161. launchAutoRunTimerPlan,
  5162. listIcloudAliases,
  5163. listLuckmailPurchasesForManagement,
  5164. normalizeHotmailAccounts,
  5165. normalizeRunCount,
  5166. AUTO_RUN_TIMER_KIND_SCHEDULED_START,
  5167. notifyStepComplete,
  5168. notifyStepError,
  5169. patchHotmailAccount,
  5170. registerTab,
  5171. requestStop,
  5172. resetState,
  5173. resumeAutoRun,
  5174. scheduleAutoRun,
  5175. selectLuckmailPurchase,
  5176. setCurrentHotmailAccount,
  5177. setEmailState,
  5178. setEmailStateSilently,
  5179. setIcloudAliasPreservedState,
  5180. setIcloudAliasUsedState,
  5181. setLuckmailPurchaseDisabledState,
  5182. setLuckmailPurchasePreservedState,
  5183. setLuckmailPurchaseUsedState,
  5184. setPersistentSettings,
  5185. setState,
  5186. setStepStatus,
  5187. skipAutoRunCountdown,
  5188. skipStep,
  5189. startAutoRunLoop,
  5190. syncHotmailAccounts,
  5191. testHotmailAccountMailAccess,
  5192. upsertHotmailAccount,
  5193. verifyHotmailAccount,
  5194. });
  5195. const stepRegistry = self.MultiPageBackgroundStepRegistry?.createStepRegistry(
  5196. stepDefinitions.map((definition) => ({
  5197. ...definition,
  5198. execute: stepExecutorsByKey[definition.key],
  5199. }))
  5200. );
  5201. async function requestOAuthUrlFromPanel(state, options = {}) {
  5202. return panelBridge.requestOAuthUrlFromPanel(state, options);
  5203. }
  5204. async function requestCpaOAuthUrl(state, options = {}) {
  5205. return panelBridge.requestCpaOAuthUrl(state, options);
  5206. }
  5207. async function requestSub2ApiOAuthUrl(state, options = {}) {
  5208. return panelBridge.requestSub2ApiOAuthUrl(state, options);
  5209. }
  5210. async function openSignupEntryTab(step = 1) {
  5211. return signupFlowHelpers.openSignupEntryTab(step);
  5212. }
  5213. async function ensureSignupEntryPageReady(step = 1) {
  5214. return signupFlowHelpers.ensureSignupEntryPageReady(step);
  5215. }
  5216. async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
  5217. return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
  5218. }
  5219. async function ensureSignupPostEmailPageReadyInTab(tabId, step = 2, options = {}) {
  5220. return signupFlowHelpers.ensureSignupPostEmailPageReadyInTab(tabId, step, options);
  5221. }
  5222. async function resolveSignupEmailForFlow(state) {
  5223. return signupFlowHelpers.resolveSignupEmailForFlow(state);
  5224. }
  5225. // ============================================================
  5226. // Step 1: Open ChatGPT homepage
  5227. // ============================================================
  5228. async function executeStep1() {
  5229. return step1Executor.executeStep1();
  5230. }
  5231. // ============================================================
  5232. // Step 2: Click signup, fill email, continue to password page
  5233. // ============================================================
  5234. async function executeStep2(state) {
  5235. return step2Executor.executeStep2(state);
  5236. }
  5237. // ============================================================
  5238. // Step 3: Fill Password (via signup-page.js)
  5239. // ============================================================
  5240. async function executeStep3(state) {
  5241. return step3Executor.executeStep3(state);
  5242. }
  5243. // ============================================================
  5244. // Step 4: Get Signup Verification Code (qq-mail.js polls, then fills in signup-page.js)
  5245. // ============================================================
  5246. function getMailConfig(state) {
  5247. const provider = state.mailProvider || 'qq';
  5248. if (provider === 'custom') {
  5249. return { provider: 'custom', label: '自定义邮箱' };
  5250. }
  5251. if (provider === HOTMAIL_PROVIDER) {
  5252. return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(API对接/本地助手)' };
  5253. }
  5254. if (provider === ICLOUD_PROVIDER) {
  5255. const configuredHost = getConfiguredIcloudHostPreference(state)
  5256. || normalizeIcloudHost(state?.preferredIcloudHost)
  5257. || 'icloud.com';
  5258. const loginUrl = getIcloudLoginUrlForHost(configuredHost) || 'https://www.icloud.com/';
  5259. const mailUrl = getIcloudMailUrlForHost(configuredHost) || loginUrl;
  5260. return {
  5261. source: 'icloud-mail',
  5262. url: mailUrl,
  5263. label: 'iCloud 邮箱',
  5264. navigateOnReuse: true,
  5265. };
  5266. }
  5267. if (provider === GMAIL_PROVIDER) {
  5268. return {
  5269. source: 'gmail-mail',
  5270. url: 'https://mail.google.com/mail/u/0/#inbox',
  5271. label: 'Gmail 邮箱',
  5272. inject: ['content/activation-utils.js', 'content/utils.js', 'content/gmail-mail.js'],
  5273. injectSource: 'gmail-mail',
  5274. };
  5275. }
  5276. if (provider === LUCKMAIL_PROVIDER) {
  5277. return { provider: LUCKMAIL_PROVIDER, label: 'LuckMail(API 购邮)' };
  5278. }
  5279. if (provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
  5280. return { provider: CLOUDFLARE_TEMP_EMAIL_PROVIDER, label: 'Cloudflare Temp Email' };
  5281. }
  5282. if (provider === '163') {
  5283. return { source: 'mail-163', url: 'https://mail.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 邮箱' };
  5284. }
  5285. if (provider === '163-vip') {
  5286. return { source: 'mail-163', url: 'https://webmail.vip.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 VIP 邮箱' };
  5287. }
  5288. if (provider === 'inbucket') {
  5289. const host = normalizeInbucketOrigin(state.inbucketHost);
  5290. const mailbox = (state.inbucketMailbox || '').trim();
  5291. if (!host) {
  5292. return { error: 'Inbucket 主机地址为空或无效。' };
  5293. }
  5294. if (!mailbox) {
  5295. return { error: 'Inbucket 邮箱名称为空。' };
  5296. }
  5297. return {
  5298. source: 'inbucket-mail',
  5299. url: `${host}/m/${encodeURIComponent(mailbox)}/`,
  5300. label: `Inbucket 邮箱(${mailbox})`,
  5301. navigateOnReuse: true,
  5302. inject: ['content/activation-utils.js', 'content/utils.js', 'content/inbucket-mail.js'],
  5303. injectSource: 'inbucket-mail',
  5304. };
  5305. }
  5306. if (provider === '2925') {
  5307. return {
  5308. source: 'mail-2925',
  5309. url: 'https://2925.com/#/mailList',
  5310. label: '2925 邮箱',
  5311. inject: ['content/utils.js', 'content/mail-2925.js'],
  5312. injectSource: 'mail-2925',
  5313. };
  5314. }
  5315. return { source: 'qq-mail', url: 'https://wx.mail.qq.com/', label: 'QQ 邮箱' };
  5316. }
  5317. function normalizeInbucketOrigin(rawValue) {
  5318. const value = (rawValue || '').trim();
  5319. if (!value) return '';
  5320. const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(value) ? value : `https://${value}`;
  5321. try {
  5322. const parsed = new URL(candidate);
  5323. return parsed.origin;
  5324. } catch {
  5325. return '';
  5326. }
  5327. }
  5328. function getVerificationCodeStateKey(step) {
  5329. return verificationFlowHelpers.getVerificationCodeStateKey(step);
  5330. }
  5331. function getVerificationCodeLabel(step) {
  5332. return verificationFlowHelpers.getVerificationCodeLabel(step);
  5333. }
  5334. async function confirmCustomVerificationStepBypass(step) {
  5335. return verificationFlowHelpers.confirmCustomVerificationStepBypass(step);
  5336. }
  5337. function getVerificationPollPayload(step, state, overrides = {}) {
  5338. return verificationFlowHelpers.getVerificationPollPayload(step, state, overrides);
  5339. }
  5340. async function requestVerificationCodeResend(step) {
  5341. return verificationFlowHelpers.requestVerificationCodeResend(step);
  5342. }
  5343. async function pollFreshVerificationCode(step, state, mail, pollOverrides = {}) {
  5344. return verificationFlowHelpers.pollFreshVerificationCode(step, state, mail, pollOverrides);
  5345. }
  5346. async function pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides = {}) {
  5347. return verificationFlowHelpers.pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides);
  5348. }
  5349. async function submitVerificationCode(step, code) {
  5350. return verificationFlowHelpers.submitVerificationCode(step, code);
  5351. }
  5352. async function resolveVerificationStep(step, state, mail, options = {}) {
  5353. return verificationFlowHelpers.resolveVerificationStep(step, state, mail, options);
  5354. }
  5355. async function executeStep4(state) {
  5356. return step4Executor.executeStep4(state);
  5357. }
  5358. // ============================================================
  5359. // Step 5: Fill Name & Birthday (via signup-page.js)
  5360. // ============================================================
  5361. async function executeStep5(state) {
  5362. return step5Executor.executeStep5(state);
  5363. }
  5364. // ============================================================
  5365. // Step 6 Cookie Cleanup
  5366. // ============================================================
  5367. function normalizeCookieDomainForMatch(domain) {
  5368. return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
  5369. }
  5370. function shouldClearPreLoginCookie(cookie) {
  5371. const domain = normalizeCookieDomainForMatch(cookie?.domain);
  5372. if (!domain) return false;
  5373. return PRE_LOGIN_COOKIE_CLEAR_DOMAINS.some((target) => (
  5374. domain === target || domain.endsWith(`.${target}`)
  5375. ));
  5376. }
  5377. function buildCookieRemovalUrl(cookie) {
  5378. const host = normalizeCookieDomainForMatch(cookie?.domain);
  5379. const path = String(cookie?.path || '/').startsWith('/')
  5380. ? String(cookie?.path || '/')
  5381. : `/${String(cookie?.path || '')}`;
  5382. return `https://${host}${path}`;
  5383. }
  5384. async function collectCookiesForPreLoginCleanup() {
  5385. if (!chrome.cookies?.getAll) {
  5386. return [];
  5387. }
  5388. const stores = chrome.cookies.getAllCookieStores
  5389. ? await chrome.cookies.getAllCookieStores()
  5390. : [{ id: undefined }];
  5391. const cookies = [];
  5392. const seen = new Set();
  5393. for (const store of stores) {
  5394. const storeId = store?.id;
  5395. const batch = await chrome.cookies.getAll(storeId ? { storeId } : {});
  5396. for (const cookie of batch || []) {
  5397. if (!shouldClearPreLoginCookie(cookie)) continue;
  5398. const key = [
  5399. cookie.storeId || storeId || '',
  5400. cookie.domain || '',
  5401. cookie.path || '',
  5402. cookie.name || '',
  5403. cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
  5404. ].join('|');
  5405. if (seen.has(key)) continue;
  5406. seen.add(key);
  5407. cookies.push(cookie);
  5408. }
  5409. }
  5410. return cookies;
  5411. }
  5412. async function removeCookieDirectly(cookie) {
  5413. const details = {
  5414. url: buildCookieRemovalUrl(cookie),
  5415. name: cookie.name,
  5416. };
  5417. if (cookie.storeId) {
  5418. details.storeId = cookie.storeId;
  5419. }
  5420. if (cookie.partitionKey) {
  5421. details.partitionKey = cookie.partitionKey;
  5422. }
  5423. try {
  5424. const result = await chrome.cookies.remove(details);
  5425. return Boolean(result);
  5426. } catch (err) {
  5427. console.warn(LOG_PREFIX, '[removeCookieDirectly] failed', {
  5428. domain: cookie?.domain,
  5429. name: cookie?.name,
  5430. message: getErrorMessage(err),
  5431. });
  5432. return false;
  5433. }
  5434. }
  5435. async function runPreStep6CookieCleanup() {
  5436. await addLog(
  5437. `步骤 6:开始前等待 ${Math.round(STEP6_PRE_LOGIN_COOKIE_CLEAR_DELAY_MS / 1000)} 秒,然后直接删除 ChatGPT / OpenAI cookies...`,
  5438. 'info'
  5439. );
  5440. await sleepWithStop(STEP6_PRE_LOGIN_COOKIE_CLEAR_DELAY_MS);
  5441. if (!chrome.cookies?.getAll || !chrome.cookies?.remove) {
  5442. await addLog('步骤 6:当前浏览器不支持 cookies API,无法直接删除 cookies。', 'warn');
  5443. return;
  5444. }
  5445. const cookies = await collectCookiesForPreLoginCleanup();
  5446. let removedCount = 0;
  5447. for (const cookie of cookies) {
  5448. throwIfStopped();
  5449. if (await removeCookieDirectly(cookie)) {
  5450. removedCount += 1;
  5451. }
  5452. }
  5453. if (chrome.browsingData?.removeCookies) {
  5454. try {
  5455. await chrome.browsingData.removeCookies({
  5456. since: 0,
  5457. origins: PRE_LOGIN_COOKIE_CLEAR_ORIGINS,
  5458. });
  5459. } catch (err) {
  5460. await addLog(`步骤 6:browsingData 补扫 cookies 失败:${getErrorMessage(err)}`, 'warn');
  5461. }
  5462. }
  5463. await addLog(`步骤 6:已直接删除 ${removedCount} 个 ChatGPT / OpenAI cookies,准备继续获取链接并登录。`, 'ok');
  5464. }
  5465. // ============================================================
  5466. // Step 7: Login and ensure the auth page reaches the login verification page
  5467. // ============================================================
  5468. async function refreshOAuthUrlBeforeStep6(state) {
  5469. await addLog(`步骤 7:正在刷新登录用的 ${getPanelModeLabel(state)} OAuth 链接...`);
  5470. console.log(LOG_PREFIX, '[refreshOAuthUrlBeforeStep6] requesting fresh OAuth directly from panel');
  5471. const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 7' });
  5472. await handleStepData(1, refreshResult);
  5473. if (!refreshResult?.oauthUrl) {
  5474. throw new Error('刷新 OAuth 链接后仍未拿到可用链接。');
  5475. }
  5476. return refreshResult.oauthUrl;
  5477. }
  5478. function isStep6SuccessResult(result) {
  5479. return result?.step6Outcome === 'success';
  5480. }
  5481. function isStep6RecoverableResult(result) {
  5482. return result?.step6Outcome === 'recoverable';
  5483. }
  5484. function isAddPhoneAuthUrl(url) {
  5485. return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)/i.test(String(url || '').trim());
  5486. }
  5487. function isAddPhoneAuthState(authState = {}) {
  5488. return authState?.state === 'add_phone_page'
  5489. || Boolean(authState?.addPhonePage)
  5490. || isAddPhoneAuthUrl(authState?.url);
  5491. }
  5492. async function getPostStep6AutoRestartDecision(step, error) {
  5493. const normalizedStep = Number(step);
  5494. const errorMessage = getErrorMessage(error);
  5495. if (!Number.isFinite(normalizedStep) || normalizedStep < 7 || normalizedStep > LAST_STEP_ID) {
  5496. return {
  5497. shouldRestart: false,
  5498. blockedByAddPhone: false,
  5499. errorMessage,
  5500. authState: null,
  5501. };
  5502. }
  5503. if (isAddPhoneAuthUrl(errorMessage)) {
  5504. return {
  5505. shouldRestart: false,
  5506. blockedByAddPhone: true,
  5507. errorMessage,
  5508. authState: null,
  5509. };
  5510. }
  5511. let authState = null;
  5512. try {
  5513. authState = await getLoginAuthStateFromContent({
  5514. logMessage: `步骤 ${normalizedStep}:正在确认当前认证页状态,以决定是否回到步骤 7 重开...`,
  5515. });
  5516. } catch (inspectError) {
  5517. console.warn(LOG_PREFIX, '[AutoRun] failed to inspect login auth state after post-step6 error', {
  5518. step: normalizedStep,
  5519. sourceError: errorMessage,
  5520. inspectError: inspectError?.message || inspectError,
  5521. });
  5522. }
  5523. if (isAddPhoneAuthState(authState)) {
  5524. return {
  5525. shouldRestart: false,
  5526. blockedByAddPhone: true,
  5527. errorMessage,
  5528. authState,
  5529. };
  5530. }
  5531. return {
  5532. shouldRestart: true,
  5533. blockedByAddPhone: false,
  5534. errorMessage,
  5535. authState,
  5536. };
  5537. }
  5538. async function getLoginAuthStateFromContent(options = {}) {
  5539. const { logMessage = '步骤 8:认证页正在切换,等待页面重新就绪后继续确认验证码页状态...' } = options;
  5540. const result = await sendToContentScriptResilient(
  5541. 'signup-page',
  5542. {
  5543. type: 'GET_LOGIN_AUTH_STATE',
  5544. source: 'background',
  5545. payload: {},
  5546. },
  5547. {
  5548. timeoutMs: 15000,
  5549. retryDelayMs: 600,
  5550. logMessage,
  5551. }
  5552. );
  5553. if (result?.error) {
  5554. throw new Error(result.error);
  5555. }
  5556. return result || {};
  5557. }
  5558. async function ensureStep8VerificationPageReady() {
  5559. const pageState = await getLoginAuthStateFromContent();
  5560. if (pageState.state === 'verification_page') {
  5561. return pageState;
  5562. }
  5563. const stateLabel = getLoginAuthStateLabel(pageState.state);
  5564. const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
  5565. throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${stateLabel}.${urlPart}`.trim());
  5566. }
  5567. async function skipLoginVerificationStepsForCpaCallback() {
  5568. await setState({
  5569. lastLoginCode: null,
  5570. loginVerificationRequestedAt: null,
  5571. });
  5572. await setStepStatus(7, 'skipped');
  5573. await addLog('步骤 7:当前已选择“第七步回调”,直接跳过步骤 7、8。', 'warn');
  5574. const latestState = await getState();
  5575. if (!isStepDoneStatus(latestState.stepStatuses?.[8])) {
  5576. await setStepStatus(8, 'skipped');
  5577. await addLog('步骤 8:当前已选择“第七步回调”,本轮无需获取登录验证码。', 'warn');
  5578. }
  5579. }
  5580. async function executeStep6() {
  5581. return step6Executor.executeStep6();
  5582. }
  5583. // ============================================================
  5584. // Step 7: Refresh OAuth and log in
  5585. // ============================================================
  5586. async function executeStep7(state) {
  5587. return step7Executor.executeStep7(state);
  5588. }
  5589. // ============================================================
  5590. // Step 8: Poll login verification mail and submit the login code
  5591. // ============================================================
  5592. async function executeStep8(state) {
  5593. return step8Executor.executeStep8(state);
  5594. }
  5595. // ============================================================
  5596. // Step 9: 完成 OAuth(自动点击 + localhost 回调监听)
  5597. // ============================================================
  5598. let webNavListener = null;
  5599. let webNavCommittedListener = null;
  5600. let step8TabUpdatedListener = null;
  5601. let step8PendingReject = null;
  5602. const STEP8_CLICK_EFFECT_TIMEOUT_MS = 15000;
  5603. const STEP8_CLICK_RETRY_DELAY_MS = 500;
  5604. const STEP8_READY_WAIT_TIMEOUT_MS = 30000;
  5605. const STEP8_MAX_ROUNDS = 5;
  5606. const STEP8_STRATEGIES = [
  5607. { mode: 'content', strategy: 'requestSubmit', label: 'form.requestSubmit' },
  5608. { mode: 'debugger', label: 'debugger click' },
  5609. { mode: 'content', strategy: 'nativeClick', label: 'element.click' },
  5610. { mode: 'content', strategy: 'dispatchClick', label: 'dispatch click' },
  5611. { mode: 'debugger', label: 'debugger click retry' },
  5612. ];
  5613. function setWebNavListener(listener) {
  5614. webNavListener = listener;
  5615. }
  5616. function getWebNavListener() {
  5617. return webNavListener;
  5618. }
  5619. function setWebNavCommittedListener(listener) {
  5620. webNavCommittedListener = listener;
  5621. }
  5622. function getWebNavCommittedListener() {
  5623. return webNavCommittedListener;
  5624. }
  5625. function setStep8TabUpdatedListener(listener) {
  5626. step8TabUpdatedListener = listener;
  5627. }
  5628. function getStep8TabUpdatedListener() {
  5629. return step8TabUpdatedListener;
  5630. }
  5631. function setStep8PendingReject(handler) {
  5632. step8PendingReject = handler;
  5633. }
  5634. function cleanupStep8NavigationListeners() {
  5635. if (webNavListener) {
  5636. chrome.webNavigation.onBeforeNavigate.removeListener(webNavListener);
  5637. webNavListener = null;
  5638. }
  5639. if (webNavCommittedListener) {
  5640. chrome.webNavigation.onCommitted.removeListener(webNavCommittedListener);
  5641. webNavCommittedListener = null;
  5642. }
  5643. if (step8TabUpdatedListener) {
  5644. chrome.tabs.onUpdated.removeListener(step8TabUpdatedListener);
  5645. step8TabUpdatedListener = null;
  5646. }
  5647. }
  5648. function rejectPendingStep8(error) {
  5649. if (!step8PendingReject) return;
  5650. const reject = step8PendingReject;
  5651. step8PendingReject = null;
  5652. reject(error);
  5653. }
  5654. function throwIfStep8SettledOrStopped(isSettled = false) {
  5655. if (isSettled || stopRequested) {
  5656. throw new Error(STOP_ERROR_MESSAGE);
  5657. }
  5658. }
  5659. async function ensureStep8SignupPageReady(tabId, options = {}) {
  5660. await ensureContentScriptReadyOnTab('signup-page', tabId, {
  5661. inject: SIGNUP_PAGE_INJECT_FILES,
  5662. injectSource: 'signup-page',
  5663. timeoutMs: options.timeoutMs ?? 15000,
  5664. retryDelayMs: options.retryDelayMs ?? 600,
  5665. logMessage: options.logMessage || '',
  5666. });
  5667. }
  5668. async function getStep8PageState(tabId, responseTimeoutMs = 1500) {
  5669. try {
  5670. const result = await sendTabMessageWithTimeout(tabId, 'signup-page', {
  5671. type: 'STEP8_GET_STATE',
  5672. source: 'background',
  5673. payload: {},
  5674. }, responseTimeoutMs);
  5675. if (result?.error) {
  5676. throw new Error(result.error);
  5677. }
  5678. return result;
  5679. } catch (err) {
  5680. if (isRetryableContentScriptTransportError(err)) {
  5681. return null;
  5682. }
  5683. throw err;
  5684. }
  5685. }
  5686. async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS) {
  5687. const start = Date.now();
  5688. let recovered = false;
  5689. let retryRecovered = false;
  5690. while (Date.now() - start < timeoutMs) {
  5691. throwIfStopped();
  5692. const pageState = await getStep8PageState(tabId);
  5693. if (pageState?.addPhonePage) {
  5694. throw new Error('步骤 9:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。');
  5695. }
  5696. if (pageState?.retryPage) {
  5697. await recoverAuthRetryPageOnTab(tabId, {
  5698. flow: 'auth',
  5699. logLabel: '步骤 9:检测到认证页重试页,正在点击“重试”恢复',
  5700. step: 8,
  5701. timeoutMs: Math.max(5000, Math.min(12000, timeoutMs)),
  5702. });
  5703. retryRecovered = true;
  5704. await sleepWithStop(250);
  5705. continue;
  5706. }
  5707. if (pageState?.consentReady) {
  5708. if (retryRecovered) {
  5709. await addLog('步骤 9:认证页重试页已恢复,准备重新定位“继续”按钮...', 'info');
  5710. }
  5711. return pageState;
  5712. }
  5713. if (pageState === null && !recovered) {
  5714. recovered = true;
  5715. await ensureStep8SignupPageReady(tabId, {
  5716. timeoutMs: Math.min(10000, timeoutMs),
  5717. logMessage: '步骤 9:认证页内容脚本已失联,正在等待页面重新就绪...',
  5718. });
  5719. continue;
  5720. }
  5721. recovered = false;
  5722. await sleepWithStop(250);
  5723. }
  5724. throw new Error('步骤 9:长时间未进入 OAuth 同意页,无法定位“继续”按钮。');
  5725. }
  5726. async function prepareStep8DebuggerClick(tabId) {
  5727. await ensureStep8SignupPageReady(tabId, {
  5728. timeoutMs: 15000,
  5729. logMessage: '步骤 9:认证页内容脚本已失联,正在恢复后继续定位按钮...',
  5730. });
  5731. const result = await sendToContentScriptResilient('signup-page', {
  5732. type: 'STEP8_FIND_AND_CLICK',
  5733. source: 'background',
  5734. payload: {},
  5735. }, {
  5736. timeoutMs: 15000,
  5737. retryDelayMs: 600,
  5738. logMessage: '步骤 9:认证页正在切换,等待 OAuth 同意页按钮重新就绪...',
  5739. });
  5740. if (result?.error) {
  5741. throw new Error(result.error);
  5742. }
  5743. return result;
  5744. }
  5745. async function triggerStep8ContentStrategy(tabId, strategy) {
  5746. await ensureStep8SignupPageReady(tabId, {
  5747. timeoutMs: 15000,
  5748. logMessage: '步骤 9:认证页内容脚本已失联,正在恢复后继续点击“继续”按钮...',
  5749. });
  5750. const result = await sendToContentScriptResilient('signup-page', {
  5751. type: 'STEP8_TRIGGER_CONTINUE',
  5752. source: 'background',
  5753. payload: {
  5754. strategy,
  5755. findTimeoutMs: 4000,
  5756. enabledTimeoutMs: 3000,
  5757. },
  5758. }, {
  5759. timeoutMs: 15000,
  5760. retryDelayMs: 600,
  5761. logMessage: '步骤 9:认证页正在切换,等待“继续”按钮重新就绪...',
  5762. });
  5763. if (result?.error) {
  5764. throw new Error(result.error);
  5765. }
  5766. return result;
  5767. }
  5768. async function recoverAuthRetryPageOnTab(tabId, payload = {}, options = {}) {
  5769. await ensureStep8SignupPageReady(tabId, {
  5770. timeoutMs: options.readyTimeoutMs ?? 15000,
  5771. retryDelayMs: options.retryDelayMs ?? 600,
  5772. logMessage: options.readyLogMessage || '步骤 9:认证页内容脚本已失联,正在恢复后继续处理重试页...',
  5773. });
  5774. const result = await sendToContentScriptResilient('signup-page', {
  5775. type: 'RECOVER_AUTH_RETRY_PAGE',
  5776. source: 'background',
  5777. payload,
  5778. }, {
  5779. timeoutMs: options.timeoutMs ?? 15000,
  5780. retryDelayMs: options.retryDelayMs ?? 600,
  5781. logMessage: options.logMessage || '步骤 9:认证页正在切换,等待“重试”按钮重新就绪...',
  5782. });
  5783. if (result?.error) {
  5784. throw new Error(result.error);
  5785. }
  5786. return result;
  5787. }
  5788. async function reloadStep8ConsentPage(tabId, timeoutMs = 30000) {
  5789. if (!Number.isInteger(tabId)) {
  5790. throw new Error('步骤 9:缺少有效的认证页标签页,无法刷新后重试。');
  5791. }
  5792. await chrome.tabs.update(tabId, { active: true }).catch(() => { });
  5793. await new Promise((resolve, reject) => {
  5794. let settled = false;
  5795. const timer = setTimeout(() => {
  5796. if (settled) return;
  5797. settled = true;
  5798. chrome.tabs.onUpdated.removeListener(listener);
  5799. reject(new Error('步骤 9:刷新认证页后等待页面完成加载超时。'));
  5800. }, timeoutMs);
  5801. const listener = (updatedTabId, changeInfo) => {
  5802. if (updatedTabId !== tabId) return;
  5803. if (changeInfo.status !== 'complete') return;
  5804. if (settled) return;
  5805. settled = true;
  5806. clearTimeout(timer);
  5807. chrome.tabs.onUpdated.removeListener(listener);
  5808. resolve();
  5809. };
  5810. chrome.tabs.onUpdated.addListener(listener);
  5811. chrome.tabs.reload(tabId, { bypassCache: false }).catch((err) => {
  5812. if (settled) return;
  5813. settled = true;
  5814. clearTimeout(timer);
  5815. chrome.tabs.onUpdated.removeListener(listener);
  5816. reject(err);
  5817. });
  5818. });
  5819. await ensureStep8SignupPageReady(tabId, {
  5820. timeoutMs: Math.min(15000, timeoutMs),
  5821. logMessage: '步骤 9:认证页刷新后内容脚本尚未就绪,正在等待页面恢复...',
  5822. });
  5823. }
  5824. async function waitForStep8ClickEffect(tabId, baselineUrl, timeoutMs = STEP8_CLICK_EFFECT_TIMEOUT_MS) {
  5825. const start = Date.now();
  5826. let recovered = false;
  5827. while (Date.now() - start < timeoutMs) {
  5828. throwIfStopped();
  5829. const tab = await chrome.tabs.get(tabId).catch(() => null);
  5830. if (!tab) {
  5831. throw new Error('步骤 9:认证页面标签页已关闭,无法继续自动授权。');
  5832. }
  5833. if (baselineUrl && typeof tab.url === 'string' && tab.url !== baselineUrl) {
  5834. return { progressed: true, reason: 'url_changed', url: tab.url };
  5835. }
  5836. const pageState = await getStep8PageState(tabId);
  5837. if (pageState?.addPhonePage) {
  5838. throw new Error('步骤 9:点击“继续”后页面跳到了手机号页面,当前流程无法继续自动授权。');
  5839. }
  5840. if (pageState?.retryPage) {
  5841. await recoverAuthRetryPageOnTab(tabId, {
  5842. flow: 'auth',
  5843. logLabel: '步骤 9:点击“继续”后进入重试页,正在点击“重试”恢复',
  5844. step: 8,
  5845. timeoutMs: Math.max(5000, Math.min(12000, timeoutMs)),
  5846. });
  5847. return {
  5848. progressed: false,
  5849. reason: 'retry_page_recovered',
  5850. restartCurrentStep: true,
  5851. url: pageState.url || baselineUrl || '',
  5852. };
  5853. }
  5854. if (pageState === null) {
  5855. if (!recovered) {
  5856. recovered = true;
  5857. await ensureStep8SignupPageReady(tabId, {
  5858. timeoutMs: Math.max(3000, Math.min(8000, timeoutMs)),
  5859. logMessage: '步骤 9:点击后认证页正在重载,正在等待内容脚本重新就绪...',
  5860. }).catch(() => null);
  5861. continue;
  5862. }
  5863. await sleepWithStop(200);
  5864. continue;
  5865. }
  5866. recovered = false;
  5867. if (pageState?.consentPage === false && !pageState?.verificationPage) {
  5868. return {
  5869. progressed: true,
  5870. reason: 'left_consent_page',
  5871. url: pageState.url || baselineUrl || '',
  5872. };
  5873. }
  5874. await sleepWithStop(200);
  5875. }
  5876. return { progressed: false, reason: 'no_effect' };
  5877. }
  5878. function getStep8EffectLabel(effect) {
  5879. switch (effect?.reason) {
  5880. case 'url_changed':
  5881. return `URL 已变化:${effect.url}`;
  5882. case 'retry_page_recovered':
  5883. return '页面进入重试页并已恢复,需要重新执行当前步骤';
  5884. case 'page_reloading':
  5885. return '页面正在跳转或重载';
  5886. case 'left_consent_page':
  5887. return `页面已离开 OAuth 同意页:${effect.url || 'unknown'}`;
  5888. default:
  5889. return '页面仍停留在 OAuth 同意页';
  5890. }
  5891. }
  5892. const step9Executor = self.MultiPageBackgroundStep9?.createStep9Executor({
  5893. addLog,
  5894. chrome,
  5895. cleanupStep8NavigationListeners,
  5896. clickWithDebugger,
  5897. completeStepFromBackground,
  5898. ensureStep8SignupPageReady,
  5899. getStep8CallbackUrlFromNavigation,
  5900. getStep8CallbackUrlFromTabUpdate,
  5901. getStep8EffectLabel,
  5902. getTabId,
  5903. getWebNavCommittedListener,
  5904. getWebNavListener,
  5905. getStep8TabUpdatedListener,
  5906. isTabAlive,
  5907. prepareStep8DebuggerClick,
  5908. reloadStep8ConsentPage,
  5909. reuseOrCreateTab,
  5910. setStep8PendingReject,
  5911. setStep8TabUpdatedListener,
  5912. setWebNavCommittedListener,
  5913. setWebNavListener,
  5914. sleepWithStop,
  5915. STEP8_CLICK_RETRY_DELAY_MS,
  5916. STEP8_MAX_ROUNDS,
  5917. STEP8_READY_WAIT_TIMEOUT_MS,
  5918. STEP8_STRATEGIES,
  5919. throwIfStep8SettledOrStopped,
  5920. triggerStep8ContentStrategy,
  5921. waitForStep8ClickEffect,
  5922. waitForStep8Ready,
  5923. });
  5924. async function executeStep9(state) {
  5925. return step9Executor.executeStep9(state);
  5926. }
  5927. // ============================================================
  5928. // Step 10: 平台回调验证
  5929. // ============================================================
  5930. async function executeStep10(state) {
  5931. return step10Executor.executeStep10(state);
  5932. }
  5933. // ============================================================
  5934. // Open Side Panel on extension icon click
  5935. // ============================================================
  5936. chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
  5937. chrome.alarms.onAlarm.addListener((alarm) => {
  5938. if (alarm.name !== AUTO_RUN_TIMER_ALARM_NAME) {
  5939. return;
  5940. }
  5941. launchAutoRunTimerPlan('alarm').catch((err) => {
  5942. console.error(LOG_PREFIX, 'Failed to resume auto run from timer alarm:', err);
  5943. });
  5944. });
  5945. chrome.runtime.onStartup.addListener(() => {
  5946. restoreAutoRunTimerIfNeeded().catch((err) => {
  5947. console.error(LOG_PREFIX, 'Failed to restore auto run timer on startup:', err);
  5948. });
  5949. });
  5950. chrome.runtime.onInstalled.addListener(() => {
  5951. restoreAutoRunTimerIfNeeded().catch((err) => {
  5952. console.error(LOG_PREFIX, 'Failed to restore auto run timer on install/update:', err);
  5953. });
  5954. });
  5955. restoreAutoRunTimerIfNeeded().catch((err) => {
  5956. console.error(LOG_PREFIX, 'Failed to restore auto run timer:', err);
  5957. });