six-degrees 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +35 -7
- package/.next/standalone/.next/required-server-files.json +6 -1
- package/.next/standalone/.next/routes-manifest.json +18 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +13 -13
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/scores/page.js +13 -0
- package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/scores.html +1 -0
- package/.next/standalone/.next/server/app/scores.meta +26 -0
- package/.next/standalone/.next/server/app/scores.rsc +24 -0
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
- package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
- package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/prefetch-hints.json +14 -0
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/.next/standalone/CHANGELOG.md +286 -0
- package/.next/standalone/CONTRIBUTING.md +15 -4
- package/.next/standalone/README.md +50 -29
- package/.next/standalone/SECURITY.md +10 -0
- package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
- package/.next/standalone/app/api/company-scores/route.js +1 -1
- package/.next/standalone/app/api/ingest/route.js +23 -2
- package/.next/standalone/app/api/outreach/route.js +107 -44
- package/.next/standalone/app/api/scraper/route.js +146 -17
- package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
- package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
- package/.next/standalone/app/api/update-images/route.js +41 -7
- package/.next/standalone/app/avatars/[file]/route.js +6 -1
- package/.next/standalone/app/components/Avatar.js +5 -2
- package/.next/standalone/app/components/ChainView.js +659 -469
- package/.next/standalone/app/components/CompanyScores.js +16 -10
- package/.next/standalone/app/components/EmptyState.js +1 -1
- package/.next/standalone/app/components/FieldStep.js +94 -0
- package/.next/standalone/app/components/ForceGraph.js +183 -768
- package/.next/standalone/app/components/GridView.js +9 -7
- package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
- package/.next/standalone/app/components/LinkedInLimits.js +22 -7
- package/.next/standalone/app/components/ListView.js +6 -4
- package/.next/standalone/app/components/MappingProgress.js +5 -6
- package/.next/standalone/app/components/OnboardingGate.js +1 -1
- package/.next/standalone/app/components/OrbitGraph.js +33 -1
- package/.next/standalone/app/components/OutlinkQuest.js +5 -4
- package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
- package/.next/standalone/app/components/RingsView.js +9 -8
- package/.next/standalone/app/components/SeparationView.js +96 -14
- package/.next/standalone/app/components/Sidebar.js +366 -287
- package/.next/standalone/app/components/TheirCircle.js +300 -0
- package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
- package/.next/standalone/app/components/settings/SectorSection.js +21 -195
- package/.next/standalone/app/components/settings/TierSection.js +99 -0
- package/.next/standalone/app/components/useRequests.js +12 -0
- package/.next/standalone/app/components/useScanner.js +13 -0
- package/.next/standalone/app/components/views.js +3 -5
- package/.next/standalone/app/globals.css +7 -0
- package/.next/standalone/app/launch/page.js +6 -3
- package/.next/standalone/app/page.js +183 -74
- package/.next/standalone/app/paths/page.js +20 -14
- package/.next/standalone/app/profile/page.js +3 -3
- package/.next/standalone/app/queue/page.js +56 -60
- package/.next/standalone/app/scores/page.js +72 -0
- package/.next/standalone/app/settings/page.js +15 -3
- package/.next/standalone/app/setup/page.js +423 -260
- package/.next/standalone/db/schema.js +2 -1
- package/.next/standalone/eslint.config.mjs +3 -3
- package/.next/standalone/install.sh +3 -3
- package/.next/standalone/lib/chain-layout.js +139 -0
- package/.next/standalone/lib/circle.js +169 -0
- package/.next/standalone/lib/companies.js +1 -1
- package/.next/standalone/lib/csv.js +21 -10
- package/.next/standalone/lib/data-export.js +2 -1
- package/.next/standalone/lib/data-folder.js +4 -2
- package/.next/standalone/lib/db-client.js +1 -0
- package/.next/standalone/lib/galaxy.js +12 -0
- package/.next/standalone/lib/ingest.js +27 -8
- package/.next/standalone/lib/legacy-offer.js +7 -6
- package/.next/standalone/lib/legacy-scores.js +2 -2
- package/.next/standalone/lib/linkedin-limits.js +30 -8
- package/.next/standalone/lib/photos.js +47 -0
- package/.next/standalone/lib/quest.js +8 -3
- package/.next/standalone/lib/rarity.js +80 -0
- package/.next/standalone/lib/reach.js +113 -0
- package/.next/standalone/lib/requests-client.js +113 -0
- package/.next/standalone/lib/rpc.js +26 -15
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scanner-setup.js +20 -1
- package/.next/standalone/lib/score-guess.js +60 -0
- package/.next/standalone/lib/scoring.js +1761 -69
- package/.next/standalone/lib/scraper-client.js +235 -17
- package/.next/standalone/lib/sector-directory.js +1 -1
- package/.next/standalone/lib/sector-focus.js +17 -3
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/settings-client.js +21 -0
- package/.next/standalone/lib/settings-effects.js +19 -1
- package/.next/standalone/lib/settings.js +7 -2
- package/.next/standalone/next.config.mjs +25 -3
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/public/demo-data.json +1 -1
- package/.next/standalone/scripts/build-app.mjs +11 -2
- package/.next/standalone/scripts/check-node.mjs +1 -1
- package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
- package/.next/standalone/scripts/image_store.py +77 -21
- package/.next/standalone/scripts/scrape.py +305 -29
- package/.next/standalone/server.js +1 -1
- package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/CHANGELOG.md +286 -0
- package/README.md +50 -29
- package/SECURITY.md +10 -0
- package/db/schema.js +2 -1
- package/lib/chain-layout.js +139 -0
- package/lib/circle.js +169 -0
- package/lib/companies.js +1 -1
- package/lib/csv.js +21 -10
- package/lib/data-export.js +2 -1
- package/lib/data-folder.js +4 -2
- package/lib/db-client.js +1 -0
- package/lib/galaxy.js +12 -0
- package/lib/ingest.js +27 -8
- package/lib/legacy-offer.js +7 -6
- package/lib/legacy-scores.js +2 -2
- package/lib/linkedin-limits.js +30 -8
- package/lib/photos.js +47 -0
- package/lib/quest.js +8 -3
- package/lib/rarity.js +80 -0
- package/lib/reach.js +113 -0
- package/lib/requests-client.js +113 -0
- package/lib/rpc.js +26 -15
- package/lib/scan-state.js +1 -0
- package/lib/scanner-setup.js +20 -1
- package/lib/score-guess.js +60 -0
- package/lib/scoring.js +1761 -69
- package/lib/scraper-client.js +235 -17
- package/lib/sector-directory.js +1 -1
- package/lib/sector-focus.js +17 -3
- package/lib/separation.js +4 -2
- package/lib/settings-client.js +21 -0
- package/lib/settings-effects.js +19 -1
- package/lib/settings.js +7 -2
- package/package.json +2 -2
- package/public/demo-data.json +1 -1
- package/scripts/check-node.mjs +1 -1
- package/scripts/image_store.py +77 -21
- package/scripts/scrape.py +305 -29
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
- package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
- package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
- package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
- package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
- package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
- package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
- package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
- package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
- package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
- package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
- package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
- package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
- package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
- package/.next/standalone/app/components/BridgeRing.js +0 -753
- package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
- package/.next/static/chunks/08k1crid-05dt.js +0 -1
- package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
- package/.next/static/chunks/0q-_khndfxubv.js +0 -2
- package/.next/static/chunks/1c79veqedvubt.js +0 -1
- package/.next/static/chunks/1chp3nx45meg5.js +0 -44
- package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
- package/.next/static/chunks/1uadqylrod5_v.js +0 -1
- package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
- package/.next/static/chunks/289ktj97di6zg.js +0 -1
- package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
- package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Six Degrees LinkedIn scanner
|
|
4
4
|
|
|
5
5
|
Drives Chrome with a persistent profile. You log into LinkedIn by hand once;
|
|
6
6
|
the session is reused on every later run.
|
|
@@ -37,7 +37,7 @@ from urllib.parse import parse_qs, urlparse
|
|
|
37
37
|
|
|
38
38
|
# Local helper: download + compress avatars into permanent WebP files so they
|
|
39
39
|
# don't break every ~3 weeks when LinkedIn's signed CDN URLs expire.
|
|
40
|
-
from image_store import localize_images
|
|
40
|
+
from image_store import TryLater, localize_images, store_avatar
|
|
41
41
|
|
|
42
42
|
# --- Config ---
|
|
43
43
|
def _load_env_local():
|
|
@@ -592,10 +592,14 @@ class LinkedInPushedBack(Exception):
|
|
|
592
592
|
|
|
593
593
|
|
|
594
594
|
class BudgetReached(Exception):
|
|
595
|
-
"""Today's or this month's search budget
|
|
595
|
+
"""Today's or this month's search budget, or today's profile views, is used.
|
|
596
|
+
|
|
597
|
+
Raised after saving. `kind` is "daily", "monthly" or "profiles"; the
|
|
598
|
+
profile cap is checked before a profile opens, so for it `found` is 0.
|
|
599
|
+
"""
|
|
596
600
|
|
|
597
601
|
def __init__(self, found=0, kind="daily"):
|
|
598
|
-
super().__init__(f"the {kind} search budget")
|
|
602
|
+
super().__init__("today's profile views" if kind == "profiles" else f"the {kind} search budget")
|
|
599
603
|
self.found = found
|
|
600
604
|
self.kind = kind
|
|
601
605
|
|
|
@@ -779,6 +783,90 @@ def push_company(people, company_name):
|
|
|
779
783
|
return inserted
|
|
780
784
|
|
|
781
785
|
|
|
786
|
+
# Failures in a row that say nothing about a link (offline, LinkedIn's image
|
|
787
|
+
# server busy or down) before Save photos stops: the rest would fail the same way.
|
|
788
|
+
PHOTO_TRIES_IN_A_ROW = 3
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def save_waiting_photos(say_none=False):
|
|
792
|
+
"""Save the photos still kept as links, once each. Returns how many were saved.
|
|
793
|
+
|
|
794
|
+
The app shows only photos saved on this computer, never LinkedIn's link, so
|
|
795
|
+
browsing never goes online. Older versions stored the link until a scan
|
|
796
|
+
replaced it, and a copy of a network made by one carries them too; those
|
|
797
|
+
people show initials until this runs, at the end of a scan or from Save
|
|
798
|
+
photos on the Scan page (--save-photos). Each link is tried until there's an
|
|
799
|
+
answer: saved, it becomes the file's path; a definite no (expired, not
|
|
800
|
+
LinkedIn's, not a picture, or the same picture as someone else's), the app
|
|
801
|
+
forgets it. A photo already saved for that person is used without fetching.
|
|
802
|
+
|
|
803
|
+
A failure that says nothing about the link (TryLater: offline, LinkedIn's
|
|
804
|
+
image server busy or down) keeps it for next time. Forgetting on those wiped
|
|
805
|
+
good links whenever this ran offline, and a person's photo then came back
|
|
806
|
+
only by scanning them again (TRAPS §7). A few in a row, or nothing but
|
|
807
|
+
those, raise TryLater once the answers there were are recorded, so the
|
|
808
|
+
run ends with the reason.
|
|
809
|
+
"""
|
|
810
|
+
try:
|
|
811
|
+
resp = requests.get(f"{APP_URL}/api/update-images", headers=app_headers(json_body=False), timeout=30)
|
|
812
|
+
except requests.RequestException as e:
|
|
813
|
+
raise SaveFailed(f"the app could not be reached ({e})")
|
|
814
|
+
if resp.status_code != 200:
|
|
815
|
+
raise SaveFailed(f"the app answered {resp.status_code}: {resp.text[:160]}")
|
|
816
|
+
waiting = resp.json().get("waiting", [])
|
|
817
|
+
if not waiting:
|
|
818
|
+
if say_none:
|
|
819
|
+
print("No photos are waiting to be saved.")
|
|
820
|
+
return 0
|
|
821
|
+
|
|
822
|
+
people = len({w.get("profileUrl") for w in waiting})
|
|
823
|
+
print(f"\nSaving {people} profile photo{'s' if people != 1 else ''} kept as links to LinkedIn...")
|
|
824
|
+
saved, forget, kept = [], [], set()
|
|
825
|
+
in_a_row, why = 0, ""
|
|
826
|
+
for w in waiting:
|
|
827
|
+
if stop_requested() or in_a_row >= PHOTO_TRIES_IN_A_ROW:
|
|
828
|
+
break
|
|
829
|
+
purl, iurl = w.get("profileUrl"), w.get("imageUrl")
|
|
830
|
+
if not purl or not iurl:
|
|
831
|
+
continue
|
|
832
|
+
try:
|
|
833
|
+
local = store_avatar(iurl, purl, overwrite=False)
|
|
834
|
+
except TryLater as e:
|
|
835
|
+
print(f" Not saved this time: {purl} — {e}")
|
|
836
|
+
kept.add(purl)
|
|
837
|
+
in_a_row, why = in_a_row + 1, str(e)
|
|
838
|
+
continue
|
|
839
|
+
in_a_row = 0
|
|
840
|
+
if local:
|
|
841
|
+
saved.append({"profileUrl": purl, "imageUrl": local})
|
|
842
|
+
else:
|
|
843
|
+
forget.append({"profileUrl": purl, "imageUrl": iurl})
|
|
844
|
+
|
|
845
|
+
# What was answered is recorded even after a stop, so it isn't tried again.
|
|
846
|
+
for i in range(0, max(len(saved), len(forget)), 100):
|
|
847
|
+
r = requests.post(
|
|
848
|
+
f"{APP_URL}/api/update-images",
|
|
849
|
+
headers=app_headers(),
|
|
850
|
+
json={"images": saved[i:i+100], "forget": forget[i:i+100]},
|
|
851
|
+
timeout=60,
|
|
852
|
+
)
|
|
853
|
+
if r.status_code != 200:
|
|
854
|
+
raise SaveFailed(f"the app answered {r.status_code}: {r.text[:160]}")
|
|
855
|
+
|
|
856
|
+
done = len({x["profileUrl"] for x in saved})
|
|
857
|
+
lost = len({x["profileUrl"] for x in forget} - {x["profileUrl"] for x in saved})
|
|
858
|
+
later = len(kept - {x["profileUrl"] for x in saved})
|
|
859
|
+
print(f" Saved {done} photo{'s' if done != 1 else ''} to this computer"
|
|
860
|
+
+ (f"; {lost} couldn't be (an expired link, a link that isn't LinkedIn's or isn't a picture, "
|
|
861
|
+
"or the same picture as someone else's), so those people show initials" if lost else "")
|
|
862
|
+
+ (f"; {later} couldn't be fetched this time, and their links are kept to try again" if later else "")
|
|
863
|
+
+ ".")
|
|
864
|
+
if not stop_requested() and (in_a_row >= PHOTO_TRIES_IN_A_ROW or (kept and not saved and not forget)):
|
|
865
|
+
raise TryLater(f"{why}. Nothing was forgotten: the photos still waiting are tried again "
|
|
866
|
+
"with Save photos, and at the end of your next scan")
|
|
867
|
+
return done
|
|
868
|
+
|
|
869
|
+
|
|
782
870
|
def scrape_full(headless=False):
|
|
783
871
|
"""Full first-time scrape: uses search page to get ALL connections with images.
|
|
784
872
|
Paginates through /search/results/people/?network=["F"] (10 results per page).
|
|
@@ -1266,16 +1354,24 @@ def note_unclear(profile_url, clear=False):
|
|
|
1266
1354
|
# day's or the month's budget is used. A cooldown lock is set when LinkedIn
|
|
1267
1355
|
# pushes back, and nothing searches until it lifts.
|
|
1268
1356
|
#
|
|
1357
|
+
# Profile views have a cap of their own, because they are what LinkedIn
|
|
1358
|
+
# restricted an account for (TRAPS §16). Every circle scan that opens someone's
|
|
1359
|
+
# profile takes one, and any two opens are at least PROFILE_GAP apart, timed
|
|
1360
|
+
# from the record, so starting scan after scan can't open profiles back to back.
|
|
1361
|
+
#
|
|
1269
1362
|
# These belong to the LinkedIn account (the one Chrome profile), not to a
|
|
1270
1363
|
# profile in the app: three app profiles must not triple the budget. The app's
|
|
1271
1364
|
# Scan page reads and edits the same files (lib/linkedin-limits.js).
|
|
1272
1365
|
#
|
|
1273
1366
|
# linkedin-activity.json {"searches": [epoch s, ...], "profiles": [...]}
|
|
1274
|
-
# scan-limits.json {"daily": 50, "monthly": 250
|
|
1367
|
+
# scan-limits.json {"daily": 50, "monthly": 250, "profiles": 50}
|
|
1368
|
+
# 0 = no cap on searches; profile views always have one
|
|
1275
1369
|
# linkedin-cooldown.json {"until": epoch s, "reason": "...", "set_at": ...}
|
|
1276
1370
|
# ---------------------------------------------------------------------------
|
|
1277
1371
|
DEFAULT_DAILY_SEARCHES = 50
|
|
1278
1372
|
DEFAULT_MONTHLY_SEARCHES = 250
|
|
1373
|
+
DEFAULT_DAILY_PROFILES = 50
|
|
1374
|
+
PROFILE_GAP = 60 # seconds between any two profile opens, whatever opened them
|
|
1279
1375
|
DAY_SECONDS = 24 * 3600
|
|
1280
1376
|
PACIFIC = "America/Los_Angeles"
|
|
1281
1377
|
|
|
@@ -1370,7 +1466,8 @@ def _read_activity():
|
|
|
1370
1466
|
except OSError:
|
|
1371
1467
|
pass
|
|
1372
1468
|
print(" (the record of LinkedIn searches couldn't be read; counting today as used)")
|
|
1373
|
-
|
|
1469
|
+
lim = search_limits()
|
|
1470
|
+
fresh = {"searches": [now] * max(1, lim["daily"] or 1), "profiles": [now] * lim["profiles"]}
|
|
1374
1471
|
try:
|
|
1375
1472
|
_write_json_atomic(path, fresh)
|
|
1376
1473
|
except Exception:
|
|
@@ -1380,17 +1477,43 @@ def _read_activity():
|
|
|
1380
1477
|
|
|
1381
1478
|
def charge_linkedin(kind="searches", n=1):
|
|
1382
1479
|
"""Write down n searches (or profile views) made just now."""
|
|
1383
|
-
|
|
1480
|
+
with _Locked("linkedin-activity"):
|
|
1481
|
+
_charge(_read_activity(), kind, n, time.time())
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
def _charge(data, kind, n, now):
|
|
1485
|
+
"""Add n of `kind` at `now` to the record and write it. The caller holds the lock."""
|
|
1384
1486
|
keep_from = month_start_pacific(now) - 32 * DAY_SECONDS
|
|
1487
|
+
data[kind] = [t for t in data.get(kind, []) if t >= keep_from] + [now] * n
|
|
1488
|
+
other = "profiles" if kind == "searches" else "searches"
|
|
1489
|
+
data[other] = [t for t in data.get(other, []) if t >= keep_from]
|
|
1490
|
+
try:
|
|
1491
|
+
_write_json_atomic(_home() / "linkedin-activity.json", data)
|
|
1492
|
+
except Exception as exc:
|
|
1493
|
+
print(f" (could not write down LinkedIn activity: {exc})")
|
|
1494
|
+
|
|
1495
|
+
|
|
1496
|
+
def take_profile_view(now=None):
|
|
1497
|
+
"""Write down one profile view, if the cap and the gap allow one now.
|
|
1498
|
+
|
|
1499
|
+
Returns 0 once it is written down and a profile may open, the whole
|
|
1500
|
+
seconds still to wait when the last view was under PROFILE_GAP ago, or
|
|
1501
|
+
None when the last 24 hours' views are used. The check and the write are
|
|
1502
|
+
one step under the lock, so two runs can't both take the last view.
|
|
1503
|
+
"""
|
|
1385
1504
|
with _Locked("linkedin-activity"):
|
|
1505
|
+
now = now if now is not None else time.time()
|
|
1386
1506
|
data = _read_activity()
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1507
|
+
views = [t for t in data["profiles"] if t > now - DAY_SECONDS]
|
|
1508
|
+
if len(views) >= search_limits()["profiles"]:
|
|
1509
|
+
return None
|
|
1510
|
+
# A time ahead of this clock (another computer's, imported) is left
|
|
1511
|
+
# out of the gap: waiting for it could never end.
|
|
1512
|
+
last = max((t for t in views if t <= now), default=None)
|
|
1513
|
+
if last is not None and now - last < PROFILE_GAP:
|
|
1514
|
+
return int(last + PROFILE_GAP - now) + 1
|
|
1515
|
+
_charge(data, "profiles", 1, now)
|
|
1516
|
+
return 0
|
|
1394
1517
|
|
|
1395
1518
|
|
|
1396
1519
|
def search_limits():
|
|
@@ -1405,7 +1528,9 @@ def search_limits():
|
|
|
1405
1528
|
except (TypeError, ValueError):
|
|
1406
1529
|
return default
|
|
1407
1530
|
return {"daily": num(data.get("daily"), DEFAULT_DAILY_SEARCHES),
|
|
1408
|
-
"monthly": num(data.get("monthly"), DEFAULT_MONTHLY_SEARCHES)
|
|
1531
|
+
"monthly": num(data.get("monthly"), DEFAULT_MONTHLY_SEARCHES),
|
|
1532
|
+
# 0 is no cap for searches, never for profile views.
|
|
1533
|
+
"profiles": num(data.get("profiles"), DEFAULT_DAILY_PROFILES) or DEFAULT_DAILY_PROFILES}
|
|
1409
1534
|
|
|
1410
1535
|
|
|
1411
1536
|
def linkedin_usage(now=None):
|
|
@@ -1427,6 +1552,11 @@ def searches_left(now=None):
|
|
|
1427
1552
|
return (left_day, "daily") if left_day <= left_month else (left_month, "monthly")
|
|
1428
1553
|
|
|
1429
1554
|
|
|
1555
|
+
def profiles_left(now=None):
|
|
1556
|
+
"""How many more profiles may be opened: the cap less the last 24 hours' views."""
|
|
1557
|
+
return max(0, search_limits()["profiles"] - linkedin_usage(now)["profiles_today"])
|
|
1558
|
+
|
|
1559
|
+
|
|
1430
1560
|
def _when(ts):
|
|
1431
1561
|
t = datetime.fromtimestamp(ts)
|
|
1432
1562
|
hour = t.hour % 12 or 12
|
|
@@ -1440,6 +1570,14 @@ def _day(ts):
|
|
|
1440
1570
|
|
|
1441
1571
|
def budget_message(kind):
|
|
1442
1572
|
lim = search_limits()
|
|
1573
|
+
if kind == "profiles":
|
|
1574
|
+
# The one that stops counting soonest frees the next view.
|
|
1575
|
+
now = time.time()
|
|
1576
|
+
views = sorted(t for t in _read_activity()["profiles"] if t > now - DAY_SECONDS)
|
|
1577
|
+
extra = len(views) - lim["profiles"]
|
|
1578
|
+
free = f" The next one frees up at {_when(views[extra] + DAY_SECONDS)}." if extra >= 0 else ""
|
|
1579
|
+
return (f"Today's profile views ({lim['profiles']}) are used — they count the last 24 hours.{free} "
|
|
1580
|
+
f"No profile was opened and nothing was recorded, so the next run starts with the same person.")
|
|
1443
1581
|
if kind == "monthly":
|
|
1444
1582
|
when = _day(next_month_start_pacific())
|
|
1445
1583
|
return (f"This month's search budget ({lim['monthly']}) is used. It starts again on {when} "
|
|
@@ -1751,6 +1889,68 @@ BRIDGE_RESULTS_JS = r"""
|
|
|
1751
1889
|
}
|
|
1752
1890
|
});
|
|
1753
1891
|
|
|
1892
|
+
// How many mutual connections each result shares with you: the line
|
|
1893
|
+
// under its card, "Maya Chen and 23 other mutual connections". Read
|
|
1894
|
+
// from the page's text in order, each count going to the result named
|
|
1895
|
+
// above it. A name two results share ("LinkedIn Member") takes none:
|
|
1896
|
+
// no count is better than someone else's. English only; any other
|
|
1897
|
+
// wording reads as no count, never a wrong one.
|
|
1898
|
+
// mutualCount: begin
|
|
1899
|
+
const mutualCount = (text) => {
|
|
1900
|
+
const l = String(text || '').replace(/\s+/g, ' ').trim();
|
|
1901
|
+
if (!/\bmutual connections?$/i.test(l) || l.length > 300) return null;
|
|
1902
|
+
const num = (s) => Number(String(s).replace(/,/g, ''));
|
|
1903
|
+
const names = (s) => s.split(/\s*,\s*|\s+and\s+/).filter(Boolean).length;
|
|
1904
|
+
let m = l.match(/^([\d,]+) mutual connections?$/i);
|
|
1905
|
+
if (m) return num(m[1]);
|
|
1906
|
+
m = l.match(/^(.*?)\s*\band ([\d,]+) others? mutual connections?$/i);
|
|
1907
|
+
if (m) return Math.max(1, names(m[1])) + num(m[2]);
|
|
1908
|
+
if (/\bis a mutual connection$/i.test(l)) return 1;
|
|
1909
|
+
m = l.match(/^(.+?)\s+are mutual connections$/i);
|
|
1910
|
+
if (m) return names(m[1]);
|
|
1911
|
+
return null;
|
|
1912
|
+
};
|
|
1913
|
+
// mutualCount: end
|
|
1914
|
+
const byName = new Map();
|
|
1915
|
+
for (const r of byHref.values()) {
|
|
1916
|
+
if (r.name) byName.set(r.name, byName.has(r.name) ? null : r);
|
|
1917
|
+
}
|
|
1918
|
+
const nameAt = (l) => {
|
|
1919
|
+
if (byName.has(l)) return byName.get(l);
|
|
1920
|
+
for (const [name, r] of byName) {
|
|
1921
|
+
if (l.startsWith(name) && /^\s*[\u2022\u00b7|,(]/.test(l.slice(name.length))) return r;
|
|
1922
|
+
}
|
|
1923
|
+
return undefined;
|
|
1924
|
+
};
|
|
1925
|
+
// The rest of a mutual line when the names are drawn above it, each on
|
|
1926
|
+
// a line of its own ("Maya Chen" / "and 23 other mutual connections").
|
|
1927
|
+
const tailOf = (l) => /^(and [\d,]+ others? mutual connections?|is a mutual connection|are mutual connections)$/i.test(l);
|
|
1928
|
+
let current = null;
|
|
1929
|
+
const give = (n) => {
|
|
1930
|
+
if (current && current.mutualCount == null && Number.isFinite(n) && n >= 1) current.mutualCount = n;
|
|
1931
|
+
};
|
|
1932
|
+
const lines = (root.innerText || '').split('\n').map(clean).filter(Boolean);
|
|
1933
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1934
|
+
const l = lines[i];
|
|
1935
|
+
// Those names are links to your own connections, so they're on the
|
|
1936
|
+
// page as people too, but they are the line, not a new result: read
|
|
1937
|
+
// them with the rest of it, and the count stays with the card it's on.
|
|
1938
|
+
let j = i;
|
|
1939
|
+
while (j < lines.length && j - i < 3 && !tailOf(lines[j]) && nameAt(lines[j]) !== undefined) j++;
|
|
1940
|
+
if (j > i && j < lines.length && tailOf(lines[j])) {
|
|
1941
|
+
give(mutualCount(`${lines.slice(i, j).join(', ')} ${lines[j]}`));
|
|
1942
|
+
i = j;
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
const n = mutualCount(l);
|
|
1946
|
+
if (n != null) {
|
|
1947
|
+
give(n);
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const hit = nameAt(l);
|
|
1951
|
+
if (hit !== undefined) current = hit;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1754
1954
|
const results = [...byHref.values()]
|
|
1755
1955
|
.filter((r) => r.name && r.name.length >= 2)
|
|
1756
1956
|
.map(({ named, ...r }) => r);
|
|
@@ -2046,6 +2246,30 @@ def _page_limit_hint(next_page):
|
|
|
2046
2246
|
return f"Run again with --deeper, and no --max-pages, to carry on from page {next_page}."
|
|
2047
2247
|
|
|
2048
2248
|
|
|
2249
|
+
def _wait_for_profile_view(page):
|
|
2250
|
+
"""Take one of today's profile views, first waiting out PROFILE_GAP.
|
|
2251
|
+
|
|
2252
|
+
None once it is taken and the profile may open; "budget" when the last 24
|
|
2253
|
+
hours' views are used; "stopped" when Stop, or a closed window, came
|
|
2254
|
+
during the wait. Nothing else is written down.
|
|
2255
|
+
"""
|
|
2256
|
+
def tick(left):
|
|
2257
|
+
if not _window_closed(page): # a closed window sets the stop
|
|
2258
|
+
print(f" {left}s to go", flush=True)
|
|
2259
|
+
|
|
2260
|
+
while True:
|
|
2261
|
+
wait = take_profile_view()
|
|
2262
|
+
if wait is None:
|
|
2263
|
+
return "budget"
|
|
2264
|
+
if not wait:
|
|
2265
|
+
return None
|
|
2266
|
+
print(f" Waiting {wait}s before opening a profile: at least {PROFILE_GAP}s pass "
|
|
2267
|
+
f"between any two.", flush=True)
|
|
2268
|
+
if not interruptible_sleep(wait, on_tick=tick, step=10) or _window_closed(page):
|
|
2269
|
+
print(" Stopped before opening their profile.")
|
|
2270
|
+
return "stopped"
|
|
2271
|
+
|
|
2272
|
+
|
|
2049
2273
|
def _scrape_one_bridge(page, bridge_name, bridge_id, profile_url, max_pages=LINKEDIN_MAX_PAGES,
|
|
2050
2274
|
start_page=1, urn=None, on_save=None):
|
|
2051
2275
|
"""
|
|
@@ -2060,6 +2284,7 @@ def _scrape_one_bridge(page, bridge_name, bridge_id, profile_url, max_pages=LINK
|
|
|
2060
2284
|
urn the id LinkedIn searches their connections by
|
|
2061
2285
|
found how many people were read in all
|
|
2062
2286
|
limited LinkedIn's monthly search limit ended the read
|
|
2287
|
+
budget "daily", "monthly" or "profiles": that budget ended it (status "budget")
|
|
2063
2288
|
|
|
2064
2289
|
Timing is calibrated from successful runs:
|
|
2065
2290
|
- 30s profile render wait (LinkedIn is slow)
|
|
@@ -2076,9 +2301,17 @@ def _scrape_one_bridge(page, bridge_name, bridge_id, profile_url, max_pages=LINK
|
|
|
2076
2301
|
# one fewer profile view, which is what LinkedIn counts most.
|
|
2077
2302
|
print(f" Going straight to {bridge_name}'s connections (their search id is known).")
|
|
2078
2303
|
else:
|
|
2079
|
-
# Step 1:
|
|
2304
|
+
# Step 1: take one of today's profile views, at least PROFILE_GAP after
|
|
2305
|
+
# the last one. With none left nothing is opened or recorded: the batch
|
|
2306
|
+
# ends (BudgetReached, which says why) and the next run tries them again.
|
|
2307
|
+
why = _wait_for_profile_view(page)
|
|
2308
|
+
if why == "budget":
|
|
2309
|
+
reach["budget"] = "profiles"
|
|
2310
|
+
if why:
|
|
2311
|
+
return [], why, reach
|
|
2312
|
+
|
|
2313
|
+
# Navigate to profile (wait_until="commit" — don't wait for full load)
|
|
2080
2314
|
print(f" Opening {bridge_name}'s profile...")
|
|
2081
|
-
charge_linkedin("profiles")
|
|
2082
2315
|
try:
|
|
2083
2316
|
page.goto(profile_url, wait_until="commit")
|
|
2084
2317
|
except Exception as e:
|
|
@@ -2437,6 +2670,10 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
|
|
|
2437
2670
|
f"Choose more than {max_pages} pages to read further.")
|
|
2438
2671
|
return _read([], "finished")
|
|
2439
2672
|
start_page, urn = nxt, (entry or {}).get("urn")
|
|
2673
|
+
# Without their search id, the read opens their profile: with no profile
|
|
2674
|
+
# views left, stop before a browser opens.
|
|
2675
|
+
if not urn and profiles_left() <= 0:
|
|
2676
|
+
raise BudgetReached(0, "profiles")
|
|
2440
2677
|
|
|
2441
2678
|
print(f"\n=== {'Carrying on with' if start_page > 1 else 'Scraping'} connections of {bridge_name} ===\n")
|
|
2442
2679
|
|
|
@@ -2506,6 +2743,17 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
|
|
|
2506
2743
|
# link; a profile that didn't render is "unclear" and marks nothing.
|
|
2507
2744
|
mark_bridge_hidden(profile_url, bridge_name)
|
|
2508
2745
|
print(" Their connections are not visible any more — keeping what is mapped.")
|
|
2746
|
+
elif status in ("private", "empty") and profile_url and not stop_requested():
|
|
2747
|
+
# A read from page 1 that found their list hidden, or LinkedIn showing no
|
|
2748
|
+
# one in it. Noted here rather than by the batch alone: a one-person scan
|
|
2749
|
+
# (a card's Scan or Rescan, the Degrees panel's Ready to scan) that found
|
|
2750
|
+
# it and said nothing left them "not scanned yet", offered again forever.
|
|
2751
|
+
# A stop that landed meanwhile concludes nothing, as in the batch. TRAPS §15.
|
|
2752
|
+
record_bridge_skip(profile_url, bridge_name,
|
|
2753
|
+
"no visible connections" if status == "private"
|
|
2754
|
+
else "LinkedIn showed no one in their list")
|
|
2755
|
+
print(" Their connections are hidden — noted, and skipped from now on."
|
|
2756
|
+
if status == "private" else " LinkedIn shows no one in their list — noted, and skipped from now on.")
|
|
2509
2757
|
|
|
2510
2758
|
if status == "stopped" or stop_requested():
|
|
2511
2759
|
return _read(read, "stopped")
|
|
@@ -2914,6 +3162,8 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
|
|
|
2914
3162
|
use, lim = linkedin_usage(), search_limits()
|
|
2915
3163
|
log(f"Search budget: {use['searches_today']} of {lim['daily'] or 'no limit'} used today, "
|
|
2916
3164
|
f"{use['searches_month']} of {lim['monthly'] or 'no limit'} this month")
|
|
3165
|
+
log(f"Profile views: {use['profiles_today']} of {lim['profiles']} in the last 24 hours, "
|
|
3166
|
+
f"at least {PROFILE_GAP}s apart")
|
|
2917
3167
|
|
|
2918
3168
|
# Get all degree-1 connections (filtered by active user)
|
|
2919
3169
|
D1_LIMIT = 20000
|
|
@@ -3095,15 +3345,11 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
|
|
|
3095
3345
|
results.append({"name": name, "tier": tier, "found": 0, "status": "private"})
|
|
3096
3346
|
if from_page > 1:
|
|
3097
3347
|
log(" Their connections are hidden now — keeping what's mapped")
|
|
3098
|
-
|
|
3099
|
-
record_bridge_skip(url, name, "no visible connections")
|
|
3100
|
-
log(" Connections are hidden — noted, and skipped from now on")
|
|
3348
|
+
# From page 1, scrape_bridge has noted them in bridge-skips.json.
|
|
3101
3349
|
elif status == "empty":
|
|
3350
|
+
# scrape_bridge has noted them in bridge-skips.json, and said so.
|
|
3102
3351
|
healthy = True
|
|
3103
3352
|
results.append({"name": name, "tier": tier, "found": 0, "status": "private"})
|
|
3104
|
-
if url:
|
|
3105
|
-
record_bridge_skip(url, name, "LinkedIn showed no one in their list")
|
|
3106
|
-
log(" LinkedIn shows no one in their list — noted, and skipped from now on")
|
|
3107
3353
|
elif status == "finished":
|
|
3108
3354
|
healthy = True
|
|
3109
3355
|
results.append({"name": name, "tier": tier, "found": 0, "status": "finished"})
|
|
@@ -3126,9 +3372,12 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
|
|
|
3126
3372
|
log("Stopped.")
|
|
3127
3373
|
raise
|
|
3128
3374
|
except BudgetReached as exc:
|
|
3129
|
-
|
|
3375
|
+
# The profile cap stops before their profile opens: nothing was
|
|
3376
|
+
# tried, so they aren't counted, and the next run tries them again.
|
|
3377
|
+
if exc.found or exc.kind != "profiles":
|
|
3378
|
+
results.append({"name": name, "tier": tier, "found": exc.found, "status": "done" if exc.found else "unclear"})
|
|
3130
3379
|
log(" " + budget_message(exc.kind))
|
|
3131
|
-
stopped_early =
|
|
3380
|
+
stopped_early = str(exc)
|
|
3132
3381
|
break
|
|
3133
3382
|
except CoolingDown as exc:
|
|
3134
3383
|
log(" " + str(exc))
|
|
@@ -3224,6 +3473,8 @@ def rescrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES):
|
|
|
3224
3473
|
raise CoolingDown(cd)
|
|
3225
3474
|
if searches_left()[0] <= 0:
|
|
3226
3475
|
raise BudgetReached(0, searches_left()[1])
|
|
3476
|
+
if profiles_left() <= 0: # a fresh read opens their profile
|
|
3477
|
+
raise BudgetReached(0, "profiles")
|
|
3227
3478
|
|
|
3228
3479
|
print(f"Deleting old cluster data...")
|
|
3229
3480
|
bridge_id = delete_bridge_cluster(bridge_name)
|
|
@@ -3423,7 +3674,7 @@ def run_server(port=5555):
|
|
|
3423
3674
|
|
|
3424
3675
|
server = HTTPServer(("127.0.0.1", port), ScrapeHandler)
|
|
3425
3676
|
print(f"\n{'='*50}")
|
|
3426
|
-
print(f"
|
|
3677
|
+
print(f" Six Degrees scanner server")
|
|
3427
3678
|
print(f" Running on http://localhost:{port}")
|
|
3428
3679
|
print(f"{'='*50}")
|
|
3429
3680
|
print(f"\n Open your visualization and use the Setup page buttons.")
|
|
@@ -3438,7 +3689,7 @@ def run_server(port=5555):
|
|
|
3438
3689
|
|
|
3439
3690
|
if __name__ == "__main__":
|
|
3440
3691
|
parser = argparse.ArgumentParser(
|
|
3441
|
-
description="
|
|
3692
|
+
description="Six Degrees LinkedIn scanner",
|
|
3442
3693
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
3443
3694
|
epilog="""
|
|
3444
3695
|
Examples:
|
|
@@ -3482,9 +3733,16 @@ Examples:
|
|
|
3482
3733
|
help="Carry on with people already mapped, from the page their last read "
|
|
3483
3734
|
"stopped at. With --auto-bridge: alongside new people. With --bridge: that person.")
|
|
3484
3735
|
parser.add_argument("--bridge-url", type=str,
|
|
3485
|
-
help="Carry on with one person, found by their LinkedIn profile URL (implies
|
|
3736
|
+
help="Carry on with one person, found by their LinkedIn profile URL (implies "
|
|
3737
|
+
"--deeper, unless --from-start)")
|
|
3738
|
+
parser.add_argument("--from-start", action="store_true",
|
|
3739
|
+
help="With --bridge-url: read their list from page 1 instead, as --bridge does. "
|
|
3740
|
+
"The app's Scan buttons use it, since two connections can share a name")
|
|
3486
3741
|
parser.add_argument("--only-unfinished", action="store_true",
|
|
3487
3742
|
help="With --auto-bridge: only people whose read was cut short (Resume all)")
|
|
3743
|
+
parser.add_argument("--save-photos", action="store_true",
|
|
3744
|
+
help="Save the profile photos still kept as links to LinkedIn, then exit "
|
|
3745
|
+
"(every scan also does this at its end)")
|
|
3488
3746
|
parser.add_argument("--headless", action="store_true", help="Run browser in headless mode")
|
|
3489
3747
|
args = parser.parse_args()
|
|
3490
3748
|
args.max_pages = max(1, min(LINKEDIN_MAX_PAGES, args.max_pages))
|
|
@@ -3493,6 +3751,8 @@ Examples:
|
|
|
3493
3751
|
def _say_why(exc_type, exc, tb):
|
|
3494
3752
|
if issubclass(exc_type, SaveFailed):
|
|
3495
3753
|
print(f"\n The app could not save what was scraped: {exc}\n", file=sys.stderr)
|
|
3754
|
+
elif issubclass(exc_type, TryLater):
|
|
3755
|
+
print(f"\n The photos weren't saved: {exc}.\n", file=sys.stderr)
|
|
3496
3756
|
elif issubclass(exc_type, NotSignedIn):
|
|
3497
3757
|
print("\n LinkedIn isn't signed in, so nothing was read. Sign in with --login, then run it again.\n",
|
|
3498
3758
|
file=sys.stderr)
|
|
@@ -3519,6 +3779,10 @@ Examples:
|
|
|
3519
3779
|
if args.login:
|
|
3520
3780
|
raise SystemExit(0 if open_login_window() else 1)
|
|
3521
3781
|
|
|
3782
|
+
if args.save_photos:
|
|
3783
|
+
save_waiting_photos(say_none=True)
|
|
3784
|
+
raise SystemExit(0)
|
|
3785
|
+
|
|
3522
3786
|
if not args.server:
|
|
3523
3787
|
resolve_active_user()
|
|
3524
3788
|
|
|
@@ -3552,7 +3816,10 @@ Examples:
|
|
|
3552
3816
|
elif args.rescrape:
|
|
3553
3817
|
rescrape_bridge(args.rescrape, headless=args.headless, max_pages=args.max_pages)
|
|
3554
3818
|
elif args.bridge_url:
|
|
3555
|
-
|
|
3819
|
+
# Carries on unless --from-start. A server started before a pull (TRAPS §22)
|
|
3820
|
+
# still sends Resume as a bare --bridge-url, and a read from page 1 would
|
|
3821
|
+
# spend searches on pages already read.
|
|
3822
|
+
scrape_bridge(None, headless=args.headless, max_pages=args.max_pages, deeper=not args.from_start,
|
|
3556
3823
|
profile_url=args.bridge_url)
|
|
3557
3824
|
elif args.bridge:
|
|
3558
3825
|
scrape_bridge(args.bridge, headless=args.headless, max_pages=args.max_pages, deeper=args.deeper)
|
|
@@ -3569,6 +3836,15 @@ Examples:
|
|
|
3569
3836
|
print("\nNo connections yet — walking your whole connections list.")
|
|
3570
3837
|
print("This takes a couple of minutes and captures photos.\n")
|
|
3571
3838
|
scrape_connections(headless=args.headless, full_walk=True)
|
|
3839
|
+
|
|
3840
|
+
# Photos an older version kept as links, saved once the scan's own are
|
|
3841
|
+
# (so a photo it just read wins). Not after a stop, which ends the run
|
|
3842
|
+
# now, and a failure here doesn't undo a scan that worked.
|
|
3843
|
+
if not args.server and not stop_requested():
|
|
3844
|
+
try:
|
|
3845
|
+
save_waiting_photos()
|
|
3846
|
+
except (SaveFailed, TryLater) as e:
|
|
3847
|
+
print(f" The photos kept as links weren't saved this time: {e}")
|
|
3572
3848
|
except (BudgetReached, CoolingDown) as exc:
|
|
3573
3849
|
print("\n " + (budget_message(exc.kind) if isinstance(exc, BudgetReached) else str(exc)))
|
|
3574
3850
|
raise SystemExit(0)
|
|
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
9
9
|
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
|
10
10
|
|
|
11
11
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
12
|
-
const nextConfig = {"env":{},"webpack":null,"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.mjs","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":false,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/six-degrees/six-degrees","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.mjs","outputFileTracingExcludes":{"*":["dist/*.app/**","dist/*.dmg","dist/staging/**","dist/electron-stage/**","docs/**","tests/**",".git",".git/**","*.log","scripts/dmg/**","scripts/pin-python-packages.mjs","desktop/**","node_modules/electron/**","node_modules/@electron/**","site/**"]},"serverExternalPackages":["node:sqlite"],"repoRoot":"/home/runner/work/six-degrees/six-degrees","turbopack":{"root":"/home/runner/work/six-degrees/six-degrees"},"distDirRoot":".next"}
|
|
12
|
+
const nextConfig = {"env":{},"webpack":null,"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.mjs","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":false,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/six-degrees/six-degrees","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.mjs","outputFileTracingExcludes":{"*":["dist/*.app/**","dist/*.dmg","dist/staging/**","dist/electron-stage/**","docs/**","tests/**",".git",".git/**","*.log","scripts/dmg/**","scripts/pin-python-packages.mjs","scripts/check-site-version.mjs","desktop/**","node_modules/electron/**","node_modules/@electron/**","site/**","*.csv","*.sqlite*","*.sixdegrees","chrome-profile/**"]},"serverExternalPackages":["node:sqlite"],"repoRoot":"/home/runner/work/six-degrees/six-degrees","turbopack":{"root":"/home/runner/work/six-degrees/six-degrees"},"distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|