six-degrees 0.4.10 → 0.5.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 +9 -9
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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 +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- 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 +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 +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 +1 -1
- 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 +3 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +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 +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 +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/social/crm/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js +10 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/crm/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/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 +15 -15
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- 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 +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__05_3-22._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__06mi9k2._.js → [root-of-the-server]__13dx4sd._.js} +7 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- 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/_1oqfuh8._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_crm_route_actions_0d1aflv.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
- package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- 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 +6 -5
- 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 +19 -3
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.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/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- 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/03bziijw5srol.js +1 -0
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/{static/chunks/1xptlvtcz6654.css → standalone/.next/static/chunks/1x_ysly42-teq.css} +1 -1
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/.next/standalone/CHANGELOG.md +235 -0
- package/.next/standalone/app/api/data/export/route.js +2 -1
- package/.next/standalone/app/api/ingest/route.js +40 -1
- package/.next/standalone/app/api/scraper/route.js +12 -2
- package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
- package/.next/standalone/app/api/social/crm/route.js +37 -0
- package/.next/standalone/app/api/social/messages/route.js +44 -8
- package/.next/standalone/app/api/social/route.js +80 -22
- package/.next/standalone/app/components/AppTabs.js +59 -0
- package/.next/standalone/app/components/ChainView.js +303 -89
- package/.next/standalone/app/components/CirclePanel.js +162 -0
- package/.next/standalone/app/components/CompanyScores.js +2 -2
- package/.next/standalone/app/components/FieldStep.js +4 -4
- package/.next/standalone/app/components/FilterPanel.js +121 -183
- package/.next/standalone/app/components/ForceGraph.js +41 -3
- package/.next/standalone/app/components/GalaxyLab.js +26 -31
- package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
- package/.next/standalone/app/components/MapControls.js +154 -0
- package/.next/standalone/app/components/NoteDetail.js +118 -0
- package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
- package/.next/standalone/app/components/PeopleMap.js +308 -0
- package/.next/standalone/app/components/ScanRadar.js +129 -0
- package/.next/standalone/app/components/ScanStatusBar.js +72 -27
- package/.next/standalone/app/components/SeparationView.js +626 -118
- package/.next/standalone/app/components/Sidebar.js +28 -2
- package/.next/standalone/app/components/settings/DataSection.js +12 -3
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
- package/.next/standalone/app/components/settings/SectorSection.js +2 -2
- package/.next/standalone/app/components/settings/TierSection.js +1 -1
- package/.next/standalone/app/components/settings/TitleSection.js +149 -0
- package/.next/standalone/app/components/views.js +15 -14
- package/.next/standalone/app/globals.css +11 -0
- package/.next/standalone/app/page.js +89 -156
- package/.next/standalone/app/paths/page.js +39 -27
- package/.next/standalone/app/profile/page.js +33 -5
- package/.next/standalone/app/queue/page.js +56 -34
- package/.next/standalone/app/scores/page.js +16 -66
- package/.next/standalone/app/settings/page.js +27 -13
- package/.next/standalone/app/setup/page.js +298 -205
- package/.next/standalone/app/social/Crm.js +683 -0
- package/.next/standalone/app/social/SocialHub.js +374 -0
- package/.next/standalone/app/social/Thread.js +90 -0
- package/.next/standalone/app/social/page.js +10 -351
- package/.next/standalone/lib/chain-layout.js +105 -35
- package/.next/standalone/lib/data-export.js +10 -4
- package/.next/standalone/lib/data-folder.js +9 -0
- package/.next/standalone/lib/data-import.js +13 -3
- package/.next/standalone/lib/degrees.js +0 -6
- package/.next/standalone/lib/dot-rings.js +25 -0
- package/.next/standalone/lib/galaxy-lab.js +65 -6
- package/.next/standalone/lib/ingest.js +2 -0
- package/.next/standalone/lib/island.js +40 -0
- package/.next/standalone/lib/linkedin-export.js +267 -46
- package/.next/standalone/lib/linkedin-limits.js +6 -2
- package/.next/standalone/lib/map-heat.js +20 -0
- package/.next/standalone/lib/network-health.js +1 -1
- package/.next/standalone/lib/notifications.js +121 -0
- package/.next/standalone/lib/people-map.js +82 -0
- package/.next/standalone/lib/rarity.js +45 -3
- package/.next/standalone/lib/reach.js +7 -6
- package/.next/standalone/lib/rpc.js +15 -4
- package/.next/standalone/lib/scan-pace.js +46 -0
- package/.next/standalone/lib/scoring.js +39 -8
- package/.next/standalone/lib/scraper-client.js +19 -6
- package/.next/standalone/lib/sector-focus.js +4 -4
- package/.next/standalone/lib/separation.js +137 -10
- package/.next/standalone/lib/settings-effects.js +20 -11
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/social-crm.js +415 -0
- package/.next/standalone/lib/social-store.js +13 -6
- package/.next/standalone/lib/tier-grid.js +99 -0
- package/.next/standalone/lib/title-ranking.js +83 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +177 -35
- package/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/{standalone/.next/static/chunks/1xptlvtcz6654.css → static/chunks/1x_ysly42-teq.css} +1 -1
- package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/CHANGELOG.md +235 -0
- package/lib/chain-layout.js +105 -35
- package/lib/data-export.js +10 -4
- package/lib/data-folder.js +9 -0
- package/lib/data-import.js +13 -3
- package/lib/degrees.js +0 -6
- package/lib/dot-rings.js +25 -0
- package/lib/galaxy-lab.js +65 -6
- package/lib/ingest.js +2 -0
- package/lib/island.js +40 -0
- package/lib/linkedin-export.js +267 -46
- package/lib/linkedin-limits.js +6 -2
- package/lib/map-heat.js +20 -0
- package/lib/network-health.js +1 -1
- package/lib/notifications.js +121 -0
- package/lib/people-map.js +82 -0
- package/lib/rarity.js +45 -3
- package/lib/reach.js +7 -6
- package/lib/rpc.js +15 -4
- package/lib/scan-pace.js +46 -0
- package/lib/scoring.js +39 -8
- package/lib/scraper-client.js +19 -6
- package/lib/sector-focus.js +4 -4
- package/lib/separation.js +137 -10
- package/lib/settings-effects.js +20 -11
- package/lib/settings.js +3 -0
- package/lib/social-crm.js +415 -0
- package/lib/social-store.js +13 -6
- package/lib/tier-grid.js +99 -0
- package/lib/title-ranking.js +83 -0
- package/package.json +1 -1
- package/scripts/scrape.py +177 -35
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
- package/.next/standalone/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/standalone/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/standalone/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- package/.next/standalone/app/components/OrbitGraph.js +0 -737
- package/.next/standalone/app/social/Conversations.js +0 -246
- package/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
package/lib/social-store.js
CHANGED
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
// messaged, when, who wrote last, the list of
|
|
5
5
|
// conversations. Never any words.
|
|
6
6
|
// social-messages-<profile>.json the messages themselves, only while Keep my
|
|
7
|
-
// messages is on (app/api/social/messages)
|
|
7
|
+
// messages is on (app/api/social/messages),
|
|
8
|
+
// and the notes sent with requests, likewise.
|
|
9
|
+
// crm-<profile>.json your own CRM: each person's stage, tags,
|
|
10
|
+
// notes and next follow-up (app/api/social/crm,
|
|
11
|
+
// lib/social-crm.js). Always kept: you wrote
|
|
12
|
+
// it. Forget it offers to delete it, asking
|
|
13
|
+
// separately, so notes aren't lost by accident.
|
|
8
14
|
//
|
|
9
15
|
// Two files so that turning Keep my messages off, or Forget it, deletes the
|
|
10
16
|
// words by deleting one file, and so the everyday file the Galaxy and the
|
|
11
|
-
// Social tab read stays small however many messages there are.
|
|
12
|
-
//
|
|
13
|
-
// (lib/data-folder.js TRAVELLING_FILES) and these aren't on it.
|
|
17
|
+
// Social tab read stays small however many messages there are. "Save a copy of my network" carries
|
|
18
|
+
// them when its Social box is ticked (lib/data-folder.js SOCIAL_FILE).
|
|
14
19
|
|
|
15
20
|
import { readFileSync, writeFileSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
16
21
|
import path from 'node:path';
|
|
@@ -30,6 +35,7 @@ export function socialFiles() {
|
|
|
30
35
|
profileId: me.id,
|
|
31
36
|
social: path.join(dir, `social-${me.id}.json`),
|
|
32
37
|
messages,
|
|
38
|
+
crm: path.join(dir, `crm-${me.id}.json`),
|
|
33
39
|
// An import's messages arrive in parts (each request stays under the 10 MB
|
|
34
40
|
// middleware reads); they gather here and replace `messages` at the end.
|
|
35
41
|
incoming: `${messages}.incoming`,
|
|
@@ -48,17 +54,18 @@ export function writeJsonAtomic(file, value) {
|
|
|
48
54
|
if (cache.file === file) cache.file = null;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
/** The kept messages
|
|
57
|
+
/** The kept messages, their live-sync previews and the requests' notes, or empty ones. */
|
|
52
58
|
export function readMessages(file) {
|
|
53
59
|
// Kept in memory while the file is unchanged: a long history is read once,
|
|
54
60
|
// not on every page of a thread or every letter typed in the search box.
|
|
55
61
|
let st;
|
|
56
|
-
try { st = statSync(file); } catch { return { threads: {}, previews: {} }; }
|
|
62
|
+
try { st = statSync(file); } catch { return { threads: {}, previews: {}, invites: {} }; }
|
|
57
63
|
if (cache.file === file && cache.mtimeMs === st.mtimeMs && cache.size === st.size) return cache.data;
|
|
58
64
|
const raw = readJson(file, {});
|
|
59
65
|
const data = {
|
|
60
66
|
threads: raw?.threads && typeof raw.threads === 'object' ? raw.threads : {},
|
|
61
67
|
previews: raw?.previews && typeof raw.previews === 'object' ? raw.previews : {},
|
|
68
|
+
invites: raw?.invites && typeof raw.invites === 'object' ? raw.invites : {},
|
|
62
69
|
};
|
|
63
70
|
Object.assign(cache, { file, mtimeMs: st.mtimeMs, size: st.size, data });
|
|
64
71
|
return data;
|
package/lib/tier-grid.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The Filters panel's grid: five tiers down, six degrees across, the same in
|
|
2
|
+
// Network Circle and in Degrees.
|
|
3
|
+
//
|
|
4
|
+
// Blake, 2026-10-02: "keeping the tiers as is but enlarging them and having 6
|
|
5
|
+
// dots to let the user tap on to toggle or not if they want specific degrees
|
|
6
|
+
// showing for whatever tier … it will be universal for both".
|
|
7
|
+
//
|
|
8
|
+
// A grid is { hidden: [tiers switched off], degrees: { S: [1, 2], … } }: a tier
|
|
9
|
+
// row switches on and off as it always did, and remembers which degrees it
|
|
10
|
+
// shows while it's off. Counts are { S: { 1: n, 2: n, … }, … }: how many people
|
|
11
|
+
// there are at each tier and degree, so a dot with nobody behind it can't be
|
|
12
|
+
// tapped, and the last people on screen can't be switched off.
|
|
13
|
+
|
|
14
|
+
export const TIERS = ['S', 'A', 'B', 'C', 'D'];
|
|
15
|
+
export const GRID_DEGREES = [1, 2, 3, 4, 5, 6];
|
|
16
|
+
|
|
17
|
+
const tierOf = (tier) => (TIERS.includes(tier) ? tier : 'D'); // no tier: with D, as the views draw it
|
|
18
|
+
|
|
19
|
+
export const makeGrid = (degrees = GRID_DEGREES) => ({
|
|
20
|
+
hidden: [],
|
|
21
|
+
degrees: Object.fromEntries(TIERS.map((t) => [t, degrees.slice()])),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Network Circle starts with your own connections; you pick the rest. */
|
|
25
|
+
export const NETWORK_GRID = Object.freeze(makeGrid([1]));
|
|
26
|
+
/** Degrees starts with everything it has. */
|
|
27
|
+
export const DEGREES_GRID = Object.freeze(makeGrid());
|
|
28
|
+
|
|
29
|
+
/** Is someone of this tier, at this degree, on screen? */
|
|
30
|
+
export function shows(grid, tier, degree) {
|
|
31
|
+
const t = tierOf(tier);
|
|
32
|
+
return !grid.hidden.includes(t) && grid.degrees[t].includes(degree);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const countAt = (counts, tier, degree) => counts?.[tier]?.[degree] || 0;
|
|
36
|
+
|
|
37
|
+
/** How many people one tier is showing. */
|
|
38
|
+
export function showingIn(grid, counts, tier) {
|
|
39
|
+
if (grid.hidden.includes(tier)) return 0;
|
|
40
|
+
return grid.degrees[tier].reduce((n, d) => n + countAt(counts, tier, d), 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** How many people the whole grid is showing. */
|
|
44
|
+
export const showing = (grid, counts) => TIERS.reduce((n, t) => n + showingIn(grid, counts, t), 0);
|
|
45
|
+
|
|
46
|
+
// A change that would leave nobody on screen isn't made.
|
|
47
|
+
const keep = (grid, next, counts) => (counts && showing(next, counts) === 0 ? grid : next);
|
|
48
|
+
|
|
49
|
+
/** One dot: that degree, for that tier. Tapping a dot in a tier that's off switches the tier on with it. */
|
|
50
|
+
export function toggleCell(grid, tier, degree, counts) {
|
|
51
|
+
const has = grid.degrees[tier].includes(degree);
|
|
52
|
+
const off = grid.hidden.includes(tier);
|
|
53
|
+
const degrees = off || !has
|
|
54
|
+
? [...new Set([...grid.degrees[tier], degree])].sort((a, b) => a - b)
|
|
55
|
+
: grid.degrees[tier].filter((d) => d !== degree);
|
|
56
|
+
return keep(grid, {
|
|
57
|
+
hidden: off ? grid.hidden.filter((t) => t !== tier) : grid.hidden,
|
|
58
|
+
degrees: { ...grid.degrees, [tier]: degrees },
|
|
59
|
+
}, counts);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A whole tier, off or back on with the degrees it had. */
|
|
63
|
+
export function toggleTier(grid, tier, counts) {
|
|
64
|
+
if (!grid.hidden.includes(tier)) return keep(grid, { ...grid, hidden: [...grid.hidden, tier] }, counts);
|
|
65
|
+
// Back on with nothing to show would look broken: give it what the others show.
|
|
66
|
+
const own = grid.degrees[tier];
|
|
67
|
+
const others = [...new Set(TIERS.flatMap((t) => (t === tier ? [] : grid.degrees[t])))].sort((a, b) => a - b);
|
|
68
|
+
return {
|
|
69
|
+
hidden: grid.hidden.filter((t) => t !== tier),
|
|
70
|
+
degrees: own.length ? grid.degrees : { ...grid.degrees, [tier]: others.length ? others : [1] },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A whole degree: off if every tier with people there is showing it, otherwise on for all of them. */
|
|
75
|
+
export function toggleDegree(grid, degree, counts) {
|
|
76
|
+
const tiers = TIERS.filter((t) => !grid.hidden.includes(t) && (!counts || countAt(counts, t, degree) > 0));
|
|
77
|
+
if (!tiers.length) return grid;
|
|
78
|
+
const allOn = tiers.every((t) => grid.degrees[t].includes(degree));
|
|
79
|
+
const degrees = { ...grid.degrees };
|
|
80
|
+
for (const t of tiers) {
|
|
81
|
+
degrees[t] = allOn ? degrees[t].filter((d) => d !== degree) : [...new Set([...degrees[t], degree])].sort((a, b) => a - b);
|
|
82
|
+
}
|
|
83
|
+
return keep(grid, { ...grid, degrees }, counts);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Every tier back on, each with the degrees it had. */
|
|
87
|
+
export const showAllTiers = (grid) => (grid.hidden.length ? { ...grid, hidden: [] } : grid);
|
|
88
|
+
|
|
89
|
+
/** People by tier and degree, for the dots: `byDegree` is { 1: rows, 2: rows, … }. */
|
|
90
|
+
export function gridCounts(byDegree = {}) {
|
|
91
|
+
const counts = Object.fromEntries(TIERS.map((t) => [t, {}]));
|
|
92
|
+
for (const [degree, rows] of Object.entries(byDegree)) {
|
|
93
|
+
for (const row of rows || []) {
|
|
94
|
+
const t = tierOf(row?.tier);
|
|
95
|
+
counts[t][degree] = (counts[t][degree] || 0) + 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return counts;
|
|
99
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Settings → Scores → Titles: how much each kind of title counts, your way
|
|
2
|
+
// (Blake, 2026-10-02: "let the user change the rankings of titles as well …
|
|
3
|
+
// incase they value certain people or is using this for outreaching new
|
|
4
|
+
// founders", and people who "find more in that role" for outbound or hiring).
|
|
5
|
+
//
|
|
6
|
+
// Two parts, both in title points (the 0–10 a title is worth before the
|
|
7
|
+
// company weighs it, lib/scoring.js LEVELS):
|
|
8
|
+
// points a level's points, yours: Owner / Entrepreneur at 10, say, to put
|
|
9
|
+
// founders of small companies level with CEOs
|
|
10
|
+
// roles words in a title, and what anyone whose title has them counts:
|
|
11
|
+
// "account executive" at 10 puts every AE you can reach in S
|
|
12
|
+
// Nothing set scores exactly as before. The model applies it (scorePerson);
|
|
13
|
+
// this file is the setting and its presets.
|
|
14
|
+
|
|
15
|
+
import { LEVELS } from './scoring.js';
|
|
16
|
+
|
|
17
|
+
/** The ladder shown in Settings, top to bottom (the main kinds of title). */
|
|
18
|
+
export const RANKED_LEVELS = ['csuite', 'owner', 'vp', 'director', 'manager', 'senior', 'ic', 'unknown', 'intern', 'student'];
|
|
19
|
+
export const NO_RANKING = Object.freeze({ points: Object.freeze({}), roles: Object.freeze([]) });
|
|
20
|
+
export const MAX_ROLES = 20;
|
|
21
|
+
const MAX_ROLE_TEXT = 60;
|
|
22
|
+
|
|
23
|
+
const clampPoints = (v) => {
|
|
24
|
+
const n = Number(v);
|
|
25
|
+
if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error('Title points go from 0 to 10.');
|
|
26
|
+
return Math.round(n * 2) / 2;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const TITLE_RANKING_SETTING = {
|
|
30
|
+
default: NO_RANKING,
|
|
31
|
+
parse(value) {
|
|
32
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('A title ranking is { points, roles }.');
|
|
33
|
+
const points = {};
|
|
34
|
+
for (const [key, v] of Object.entries(value.points || {})) {
|
|
35
|
+
if (!Object.hasOwn(LEVELS, key)) throw new Error(`There is no kind of title called '${key}'.`);
|
|
36
|
+
const p = clampPoints(v);
|
|
37
|
+
if (p !== LEVELS[key].points) points[key] = p; // the default needn't be stored
|
|
38
|
+
}
|
|
39
|
+
const roles = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const r of Array.isArray(value.roles) ? value.roles : []) {
|
|
42
|
+
const text = String(r?.text ?? '').trim().toLowerCase().replace(/\s+/g, ' ');
|
|
43
|
+
if (text.length < 2) throw new Error('A role needs at least two letters.');
|
|
44
|
+
if (text.length > MAX_ROLE_TEXT) throw new Error(`Keep a role under ${MAX_ROLE_TEXT} letters.`);
|
|
45
|
+
if (seen.has(text)) continue;
|
|
46
|
+
seen.add(text);
|
|
47
|
+
roles.push({ text, points: clampPoints(r.points) });
|
|
48
|
+
}
|
|
49
|
+
if (roles.length > MAX_ROLES) throw new Error(`Up to ${MAX_ROLES} roles.`);
|
|
50
|
+
return { points, roles };
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The same ranking always gives the same text, so the stored scores can say which they were made with. */
|
|
55
|
+
export function rankingFingerprint(r = NO_RANKING) {
|
|
56
|
+
const points = Object.keys(r.points || {}).sort().map((k) => `${k}=${r.points[k]}`).join(',');
|
|
57
|
+
const roles = (r.roles || []).map((x) => `${x.text}=${x.points}`).sort().join(',');
|
|
58
|
+
return points || roles ? `${points}|${roles}` : 'none';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const isDefaultRanking = (r) => rankingFingerprint(r) === 'none';
|
|
62
|
+
|
|
63
|
+
/** Starting points for the common reasons to change it. Each replaces the whole ranking; you can adjust from there. */
|
|
64
|
+
export const RANKING_PRESETS = [
|
|
65
|
+
{ key: 'default', label: 'As the app ranks them', ranking: NO_RANKING, note: 'C-suite and founders first, then VPs, directors, managers.' },
|
|
66
|
+
{
|
|
67
|
+
key: 'founders', label: 'Founders first',
|
|
68
|
+
note: 'Owners and entrepreneurs count like CEOs, and anyone who says founder counts as much: for meeting founders, or selling to them.',
|
|
69
|
+
ranking: { points: { owner: 10 }, roles: [{ text: 'founder', points: 10 }, { text: 'co-founder', points: 10 }] },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'investors', label: 'Investors',
|
|
73
|
+
note: 'Anyone who invests for a living rises to the top: for raising money.',
|
|
74
|
+
ranking: { points: {}, roles: ['investor', 'venture', 'angel', 'general partner', 'managing partner', 'principal at', 'vc'].map((text) => ({ text, points: 10 })) },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'role', label: 'A role I’m looking for',
|
|
78
|
+
note: 'For outbound or hiring: add the role below, and everyone in it rises, whatever their level.',
|
|
79
|
+
ranking: null, // keeps what you have and opens the role box
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
export { titleHas } from './scoring.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Map your professional network as a galaxy — leverage tiers, bridges into new circles, and the shortest path to anyone. Runs locally on your own data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blake Burford",
|
package/scripts/scrape.py
CHANGED
|
@@ -59,7 +59,18 @@ _ENV_LOCAL = _load_env_local()
|
|
|
59
59
|
# a server they don't control.
|
|
60
60
|
APP_URL = os.getenv("APP_URL", "http://localhost:3000")
|
|
61
61
|
MESSAGING_URL = "https://www.linkedin.com/messaging/"
|
|
62
|
+
# The daily sync reads the top of your messages list: this many scrolls.
|
|
62
63
|
MESSAGE_SCROLLS = 15
|
|
64
|
+
# Full history (--messages --full-history, the Social tab's "Read my whole
|
|
65
|
+
# history"): keep scrolling until the list stops growing, but never past these
|
|
66
|
+
# ceilings, at the same fixed pace as everything else the scanner does.
|
|
67
|
+
MESSAGE_HISTORY_SCROLLS = 60
|
|
68
|
+
MESSAGE_HISTORY_MAX = 1000
|
|
69
|
+
# Scrolls in a row that bring no conversation it hadn't seen: the end of the list.
|
|
70
|
+
MESSAGE_HISTORY_STILL = 3
|
|
71
|
+
# Seconds after each scroll for the list to load. Fixed, on purpose: the
|
|
72
|
+
# scanner is openly slow, never made to look like a person with random waits.
|
|
73
|
+
MESSAGE_WAIT = 4
|
|
63
74
|
|
|
64
75
|
CONNECTIONS_URL = "https://www.linkedin.com/mynetwork/invite-connect/connections/"
|
|
65
76
|
|
|
@@ -80,7 +91,7 @@ def _assert_local_target():
|
|
|
80
91
|
if host in ("localhost", "127.0.0.1", "::1", "0.0.0.0"):
|
|
81
92
|
return
|
|
82
93
|
if os.getenv("ALLOW_REMOTE_PUSH") == "1":
|
|
83
|
-
print(f"WARNING: pushing
|
|
94
|
+
print(f"WARNING: pushing scanned data to remote host {host}")
|
|
84
95
|
return
|
|
85
96
|
raise SystemExit(
|
|
86
97
|
f"Refusing to push scraped data to '{APP_URL}'.\n"
|
|
@@ -359,7 +370,7 @@ def _print_sign_in_banner(say):
|
|
|
359
370
|
print(" inside automated browsers, which is why that window comes up")
|
|
360
371
|
print(" greyed out and does nothing.")
|
|
361
372
|
print()
|
|
362
|
-
print(" Nothing is
|
|
373
|
+
print(" Nothing is scanned until you are signed in, and the scan starts")
|
|
363
374
|
print(" by itself the moment you are. Take as long as you need.")
|
|
364
375
|
print(" Close the browser window to cancel.")
|
|
365
376
|
print(" ==================================================================")
|
|
@@ -376,11 +387,11 @@ def _wait_for_sign_in(page, context, timeout_s, say):
|
|
|
376
387
|
# not: signing in legitimately opens and closes tabs.
|
|
377
388
|
try:
|
|
378
389
|
if not [pg for pg in context.pages if not pg.is_closed()]:
|
|
379
|
-
print("\n Browser closed — nothing was
|
|
390
|
+
print("\n Browser closed — nothing was scanned.")
|
|
380
391
|
say("Browser closed before sign-in.")
|
|
381
392
|
return False
|
|
382
393
|
except Exception:
|
|
383
|
-
print("\n Browser closed — nothing was
|
|
394
|
+
print("\n Browser closed — nothing was scanned.")
|
|
384
395
|
return False
|
|
385
396
|
|
|
386
397
|
# The cookie belongs to the whole browser, so it is found no matter which
|
|
@@ -482,7 +493,7 @@ def resolve_active_user():
|
|
|
482
493
|
for u in users:
|
|
483
494
|
if (u.get("name") or "").strip().lower() == wanted:
|
|
484
495
|
_active_user_id = u["id"]
|
|
485
|
-
print(f"
|
|
496
|
+
print(f" Scanning into profile: {u['name']}")
|
|
486
497
|
return _active_user_id
|
|
487
498
|
raise SystemExit(f"No profile named '{os.getenv('SIX_DEGREES_USER')}' in the app.")
|
|
488
499
|
|
|
@@ -499,7 +510,7 @@ def resolve_active_user():
|
|
|
499
510
|
raise SystemExit(f"The app at {APP_URL} did not say which profile to use.")
|
|
500
511
|
|
|
501
512
|
_active_user_id = me["id"]
|
|
502
|
-
print(f"
|
|
513
|
+
print(f" Scanning into profile: {me.get('name')}")
|
|
503
514
|
return _active_user_id
|
|
504
515
|
|
|
505
516
|
|
|
@@ -721,7 +732,7 @@ def push_connections(connections, degree=1, bridge_id=None, user_id=None, on_sav
|
|
|
721
732
|
if p["profile_url"] in d1_urls:
|
|
722
733
|
# Unlock via API route
|
|
723
734
|
requests.post(f"{APP_URL}/api/unlock", json={"connectionId": p["id"]}, headers=app_headers())
|
|
724
|
-
print(f" *** PATH UNLOCKED: {p['name']}! Run --bridge \"{p['name']}\" to
|
|
735
|
+
print(f" *** PATH UNLOCKED: {p['name']}! Run --bridge \"{p['name']}\" to scan their network ***")
|
|
725
736
|
|
|
726
737
|
return inserted
|
|
727
738
|
|
|
@@ -882,8 +893,8 @@ def scrape_full(headless=False):
|
|
|
882
893
|
if searches_left()[0] <= 0:
|
|
883
894
|
raise BudgetReached(0, searches_left()[1])
|
|
884
895
|
|
|
885
|
-
print("\n=== Full
|
|
886
|
-
print("This
|
|
896
|
+
print("\n=== Full account scan ===\n")
|
|
897
|
+
print("This scans ALL connections via the search page (not the connections page).")
|
|
887
898
|
print("Takes 3-5 minutes for ~500 connections.\n")
|
|
888
899
|
|
|
889
900
|
with sync_playwright() as p:
|
|
@@ -1275,6 +1286,18 @@ SAVE_EVERY_PAGES = 10 # save as a long read goes, so a stop loses li
|
|
|
1275
1286
|
# randomised: the point is fewer searches an hour, not looking like a person.
|
|
1276
1287
|
PAGE_PAUSE = 20 # seconds before each next page of results
|
|
1277
1288
|
CHUNK_COOLDOWN = 60 # extra seconds after every SAVE_EVERY_PAGES pages
|
|
1289
|
+
# The Scan page's speed (Blake, 2026-09-30 and 10-03: "slow, medium and fast").
|
|
1290
|
+
# Fast is exactly the pacing above and nothing is ever faster; Medium and Slow
|
|
1291
|
+
# only add waiting. Speed changes how many searches an hour, never how many a
|
|
1292
|
+
# day: the daily budget stays the volume cap. Read from scan-limits.json
|
|
1293
|
+
# ("pace") at the start of every run (apply_pace). lib/scan-pace.js holds the
|
|
1294
|
+
# same numbers for the page, and tests/scan-pace.test.mjs checks they agree.
|
|
1295
|
+
SCAN_PACES = {
|
|
1296
|
+
"fast": {"page_pause": 20, "chunk_cooldown": 60, "profile_gap": 60},
|
|
1297
|
+
"medium": {"page_pause": 45, "chunk_cooldown": 180, "profile_gap": 90},
|
|
1298
|
+
"slow": {"page_pause": 90, "chunk_cooldown": 300, "profile_gap": 120},
|
|
1299
|
+
}
|
|
1300
|
+
DEFAULT_PACE = "fast"
|
|
1278
1301
|
LEGACY_PAGES_READ = 10 # how far every read before 0.1.6 went, at most
|
|
1279
1302
|
|
|
1280
1303
|
# Experimental all-day pacing for Auto-Bridge (--experimental; item 44, Graph
|
|
@@ -1550,6 +1573,27 @@ def _profile_wait(data, now):
|
|
|
1550
1573
|
return 0
|
|
1551
1574
|
|
|
1552
1575
|
|
|
1576
|
+
def scan_pace():
|
|
1577
|
+
"""The speed saved on the Scan page: "fast", "medium" or "slow"."""
|
|
1578
|
+
try:
|
|
1579
|
+
name = json.loads((_home() / "scan-limits.json").read_text()).get("pace")
|
|
1580
|
+
except Exception:
|
|
1581
|
+
name = None
|
|
1582
|
+
return name if name in SCAN_PACES else DEFAULT_PACE
|
|
1583
|
+
|
|
1584
|
+
|
|
1585
|
+
def apply_pace(name=None):
|
|
1586
|
+
"""Set this run's waits from the chosen speed. Never below Fast's."""
|
|
1587
|
+
global PAGE_PAUSE, CHUNK_COOLDOWN, PROFILE_GAP
|
|
1588
|
+
name = name if name in SCAN_PACES else scan_pace()
|
|
1589
|
+
pace = SCAN_PACES[name]
|
|
1590
|
+
fast = SCAN_PACES["fast"]
|
|
1591
|
+
PAGE_PAUSE = max(fast["page_pause"], pace["page_pause"])
|
|
1592
|
+
CHUNK_COOLDOWN = max(fast["chunk_cooldown"], pace["chunk_cooldown"])
|
|
1593
|
+
PROFILE_GAP = max(fast["profile_gap"], pace["profile_gap"])
|
|
1594
|
+
return name
|
|
1595
|
+
|
|
1596
|
+
|
|
1553
1597
|
def search_limits():
|
|
1554
1598
|
try:
|
|
1555
1599
|
data = json.loads((_home() / "scan-limits.json").read_text())
|
|
@@ -2068,14 +2112,54 @@ def voyager_conversations(payload):
|
|
|
2068
2112
|
return out
|
|
2069
2113
|
|
|
2070
2114
|
|
|
2071
|
-
def
|
|
2115
|
+
def conversation_id(c):
|
|
2116
|
+
"""One conversation, however many of LinkedIn's responses mention it: its link, else who's in it."""
|
|
2117
|
+
return c.get("url") or ",".join(c.get("people") or [])
|
|
2118
|
+
|
|
2119
|
+
|
|
2120
|
+
def tally_conversations(seen, convos):
|
|
2121
|
+
"""Add these conversations to `seen` (id -> conversation, the newest activity
|
|
2122
|
+
kept) and say how many it didn't have before. The full-history read stops
|
|
2123
|
+
when scrolling stops bringing new ones."""
|
|
2124
|
+
new = 0
|
|
2125
|
+
for c in convos:
|
|
2126
|
+
k = conversation_id(c)
|
|
2127
|
+
if not k:
|
|
2128
|
+
continue
|
|
2129
|
+
if k not in seen:
|
|
2130
|
+
new += 1
|
|
2131
|
+
seen[k] = c
|
|
2132
|
+
elif c.get("at", 0) >= seen[k].get("at", 0):
|
|
2133
|
+
seen[k] = c
|
|
2134
|
+
return new
|
|
2135
|
+
|
|
2136
|
+
|
|
2137
|
+
def history_done(new_per_scroll, found, max_scrolls=MESSAGE_HISTORY_SCROLLS,
|
|
2138
|
+
max_found=MESSAGE_HISTORY_MAX, still=MESSAGE_HISTORY_STILL):
|
|
2139
|
+
"""Why a full-history read should stop now, or None to scroll again.
|
|
2140
|
+
`new_per_scroll` is how many new conversations each scroll so far brought;
|
|
2141
|
+
`found` how many there are in all."""
|
|
2142
|
+
if found >= max_found:
|
|
2143
|
+
return f"reached {max_found:,} conversations, the most one read takes"
|
|
2144
|
+
if len(new_per_scroll) >= max_scrolls:
|
|
2145
|
+
return f"reached {max_scrolls} scrolls, the most one read takes"
|
|
2146
|
+
if len(new_per_scroll) >= still and not any(new_per_scroll[-still:]):
|
|
2147
|
+
return "the list stopped growing: that's the whole history"
|
|
2148
|
+
return None
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
def sync_messages(headless=False, full_history=False):
|
|
2072
2152
|
"""Experimental: read your LinkedIn messages list once, for the Social tab. Opens
|
|
2073
2153
|
Messaging in your own Chrome, scrolls the list a few times, and keeps from LinkedIn's
|
|
2074
|
-
own data who each
|
|
2075
|
-
unread messages, its link and who wrote last. The newest message's words
|
|
2076
|
-
app only while Keep my messages is on there. No search budget.
|
|
2154
|
+
own data who each conversation is with, when it was last active, whether it has
|
|
2155
|
+
unread messages, its link and who wrote last. The newest message's words (1:1 only)
|
|
2156
|
+
go to the app only while Keep my messages is on there. No search budget.
|
|
2157
|
+
|
|
2158
|
+
full_history: keep scrolling until no new conversations load (history_done), for
|
|
2159
|
+
the whole list rather than its top. Same fixed waits; opens no conversation."""
|
|
2077
2160
|
from playwright.sync_api import sync_playwright
|
|
2078
2161
|
kept = []
|
|
2162
|
+
seen = {}
|
|
2079
2163
|
with sync_playwright() as p:
|
|
2080
2164
|
browser = p.chromium.launch_persistent_context(
|
|
2081
2165
|
user_data_dir=get_scraper_profile_path(), headless=headless, channel="chrome",
|
|
@@ -2086,13 +2170,38 @@ def sync_messages(headless=False):
|
|
|
2086
2170
|
browser.close()
|
|
2087
2171
|
return
|
|
2088
2172
|
page.on("response", lambda r: kept.append(r) if "/voyager/api/" in r.url and "messag" in r.url.lower() else None)
|
|
2089
|
-
|
|
2173
|
+
|
|
2174
|
+
def wait(seconds):
|
|
2175
|
+
# Playwright hands over the responses that came in only while it's
|
|
2176
|
+
# being called, so wait through it, not with time.sleep.
|
|
2177
|
+
try:
|
|
2178
|
+
page.wait_for_timeout(seconds * 1000)
|
|
2179
|
+
except Exception:
|
|
2180
|
+
time.sleep(seconds)
|
|
2181
|
+
|
|
2182
|
+
def read_new():
|
|
2183
|
+
got = kept[:]
|
|
2184
|
+
del kept[:len(got)]
|
|
2185
|
+
convos = []
|
|
2186
|
+
for r in got:
|
|
2187
|
+
try:
|
|
2188
|
+
data = r.json()
|
|
2189
|
+
except Exception:
|
|
2190
|
+
continue
|
|
2191
|
+
_wire_sample(data, prefix="messages")
|
|
2192
|
+
convos.extend(voyager_conversations(data))
|
|
2193
|
+
return tally_conversations(seen, convos)
|
|
2194
|
+
|
|
2195
|
+
print("Opening your messages list" + (", to read its whole history..." if full_history else "..."))
|
|
2090
2196
|
try:
|
|
2091
2197
|
page.goto(MESSAGING_URL, wait_until="domcontentloaded")
|
|
2092
2198
|
except Exception:
|
|
2093
2199
|
pass
|
|
2094
|
-
|
|
2095
|
-
|
|
2200
|
+
wait(6)
|
|
2201
|
+
read_new()
|
|
2202
|
+
scrolls = MESSAGE_HISTORY_SCROLLS if full_history else MESSAGE_SCROLLS
|
|
2203
|
+
new_per_scroll = []
|
|
2204
|
+
for i in range(scrolls):
|
|
2096
2205
|
if stop_requested() or _window_closed(page):
|
|
2097
2206
|
break
|
|
2098
2207
|
try:
|
|
@@ -2105,19 +2214,21 @@ def sync_messages(headless=False):
|
|
|
2105
2214
|
if (l) l.scrollTop = l.scrollHeight; }""")
|
|
2106
2215
|
except Exception:
|
|
2107
2216
|
pass
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2217
|
+
wait(MESSAGE_WAIT)
|
|
2218
|
+
new_per_scroll.append(read_new())
|
|
2219
|
+
if full_history:
|
|
2220
|
+
print(f" Reading the list (scroll {i + 1}, {len(seen):,} conversations so far)...", flush=True)
|
|
2221
|
+
why = history_done(new_per_scroll, len(seen))
|
|
2222
|
+
if why:
|
|
2223
|
+
print(f" Stopping: {why}.", flush=True)
|
|
2224
|
+
break
|
|
2225
|
+
else:
|
|
2226
|
+
print(f" Reading the list ({i + 1} of {scrolls})...", flush=True)
|
|
2118
2227
|
browser.close()
|
|
2119
|
-
|
|
2120
|
-
|
|
2228
|
+
convos = list(seen.values())
|
|
2229
|
+
me, people = live_people(convos)
|
|
2230
|
+
groups = live_groups(convos, me)
|
|
2231
|
+
print(f" Found {len(convos)} conversations in LinkedIn's data: {len(people)} with one person, {len(groups)} groups.")
|
|
2121
2232
|
# The newest message's words go to the app only if Keep my messages is on
|
|
2122
2233
|
# there. The app checks too; this way they don't even leave the scanner.
|
|
2123
2234
|
keep = False
|
|
@@ -2131,14 +2242,37 @@ def sync_messages(headless=False):
|
|
|
2131
2242
|
if v.get("preview"):
|
|
2132
2243
|
v["preview"]["text"] = None
|
|
2133
2244
|
try:
|
|
2134
|
-
r = requests.put(f"{APP_URL}/api/social", json={"live": people}, headers=app_headers(), timeout=
|
|
2245
|
+
r = requests.put(f"{APP_URL}/api/social", json={"live": people, "groups": groups}, headers=app_headers(), timeout=60)
|
|
2135
2246
|
got = r.json() if r.ok else {}
|
|
2136
2247
|
matched, unmatched = got.get("people", 0), got.get("unmatched", 0)
|
|
2137
|
-
print(f" Saved for the Social tab: {matched}
|
|
2248
|
+
print(f" Saved for the Social tab: {matched} of your connections, {unmatched} people who aren't connections, "
|
|
2249
|
+
f"{len(groups)} groups.")
|
|
2138
2250
|
except Exception as e:
|
|
2139
2251
|
print(f" Couldn't save them: {e}")
|
|
2140
2252
|
|
|
2141
2253
|
|
|
2254
|
+
def live_groups(convos, me):
|
|
2255
|
+
"""The messages sync's group conversations: who's in them (everyone but you,
|
|
2256
|
+
with their names for the app to match to your connections; it keeps the names
|
|
2257
|
+
only of people who aren't), when each was last active, unread, its link and
|
|
2258
|
+
whether someone else wrote last. No words: a group's newest message isn't sent."""
|
|
2259
|
+
groups = {}
|
|
2260
|
+
for c in convos:
|
|
2261
|
+
others = [u for u in c["people"] if u != me]
|
|
2262
|
+
if len(others) < 2:
|
|
2263
|
+
continue
|
|
2264
|
+
k = conversation_id(c)
|
|
2265
|
+
if k in groups and c["at"] <= groups[k]["last"]:
|
|
2266
|
+
continue
|
|
2267
|
+
latest = c.get("latest")
|
|
2268
|
+
sender = latest.get("from") if latest else None
|
|
2269
|
+
groups[k] = {"threadUrl": c.get("url"), "people": others,
|
|
2270
|
+
"names": {u: n for u, n in (c.get("names") or {}).items() if u in others},
|
|
2271
|
+
"last": c["at"], "unread": c["unread"],
|
|
2272
|
+
"lastFromThem": (sender != me) if sender in c["people"] else None}
|
|
2273
|
+
return list(groups.values())
|
|
2274
|
+
|
|
2275
|
+
|
|
2142
2276
|
def live_people(convos):
|
|
2143
2277
|
"""The messages sync's conversations, per person: you are the one in the most
|
|
2144
2278
|
conversations, and only the 1:1 ones are kept. For each person, their newest
|
|
@@ -3155,7 +3289,7 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
|
|
|
3155
3289
|
if not urn and profiles_left() <= 0:
|
|
3156
3290
|
raise BudgetReached(0, "profiles")
|
|
3157
3291
|
|
|
3158
|
-
print(f"\n=== {'Carrying on with' if start_page > 1 else '
|
|
3292
|
+
print(f"\n=== {'Carrying on with' if start_page > 1 else 'Scanning'} connections of {bridge_name} ===\n")
|
|
3159
3293
|
|
|
3160
3294
|
read = []
|
|
3161
3295
|
|
|
@@ -3267,7 +3401,7 @@ def scrape_company(company_name, headless=False, log_fn=None):
|
|
|
3267
3401
|
if log_fn:
|
|
3268
3402
|
log_fn(msg)
|
|
3269
3403
|
|
|
3270
|
-
log(f"
|
|
3404
|
+
log(f"Scanning employees at: {company_name}")
|
|
3271
3405
|
|
|
3272
3406
|
# A company scan is people searches too: the same cooldown and budget.
|
|
3273
3407
|
cd = read_cooldown()
|
|
@@ -3944,7 +4078,7 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
|
|
|
3944
4078
|
|
|
3945
4079
|
def rescrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES):
|
|
3946
4080
|
"""Delete all existing cluster data for a bridge and re-scrape from scratch."""
|
|
3947
|
-
print(f"\n===
|
|
4081
|
+
print(f"\n=== Rescanning {bridge_name} (delete + fresh scan) ===\n")
|
|
3948
4082
|
|
|
3949
4083
|
# Check before deleting anything: a cooldown or an empty budget would leave
|
|
3950
4084
|
# their circle deleted and not read again.
|
|
@@ -4207,6 +4341,9 @@ Examples:
|
|
|
4207
4341
|
parser.add_argument("--company", type=str, help="Scan everyone the app can see at one company")
|
|
4208
4342
|
parser.add_argument("--messages", action="store_true",
|
|
4209
4343
|
help="Experimental: read your messages list once for the Social tab (who, when, unread; the newest message's words only if Keep my messages is on)")
|
|
4344
|
+
parser.add_argument("--full-history", action="store_true",
|
|
4345
|
+
help=f"With --messages: keep scrolling until the whole list has loaded (at most {MESSAGE_HISTORY_SCROLLS} scrolls "
|
|
4346
|
+
f"or {MESSAGE_HISTORY_MAX:,} conversations), not just its top")
|
|
4210
4347
|
parser.add_argument("--experimental", action="store_true",
|
|
4211
4348
|
help="Auto-Bridge, experimental: all-day pacing (sittings, rests, daytime only, "
|
|
4212
4349
|
"waits for the daily budget) and reading LinkedIn's own data beside the page text")
|
|
@@ -4245,11 +4382,16 @@ Examples:
|
|
|
4245
4382
|
EXPERIMENT["on"] = True
|
|
4246
4383
|
print("Experimental Auto-Bridge: all-day pacing, and LinkedIn's own data read beside the page text.")
|
|
4247
4384
|
args.max_pages = max(1, min(LINKEDIN_MAX_PAGES, args.max_pages))
|
|
4385
|
+
# The Scan page's speed. Signing in searches nothing, so it isn't said there.
|
|
4386
|
+
_pace = apply_pace()
|
|
4387
|
+
if not args.login:
|
|
4388
|
+
print(f"Speed: {_pace.capitalize()}. {PAGE_PAUSE}s before each page of results, {CHUNK_COOLDOWN}s more after "
|
|
4389
|
+
f"every {SAVE_EVERY_PAGES}, and profiles at least {PROFILE_GAP}s apart.")
|
|
4248
4390
|
|
|
4249
4391
|
# A failed save ends the run with its reason, not a traceback (TRAPS §32).
|
|
4250
4392
|
def _say_why(exc_type, exc, tb):
|
|
4251
4393
|
if issubclass(exc_type, SaveFailed):
|
|
4252
|
-
print(f"\n The app could not save what was
|
|
4394
|
+
print(f"\n The app could not save what was scanned: {exc}\n", file=sys.stderr)
|
|
4253
4395
|
elif issubclass(exc_type, TryLater):
|
|
4254
4396
|
print(f"\n The photos weren't saved: {exc}.\n", file=sys.stderr)
|
|
4255
4397
|
elif issubclass(exc_type, NotSignedIn):
|
|
@@ -4305,7 +4447,7 @@ Examples:
|
|
|
4305
4447
|
done = sum(1 for r in results if r.get("status") == "done")
|
|
4306
4448
|
print(f"\nDone. {done}/{len(results)} bridges mapped.")
|
|
4307
4449
|
elif args.messages:
|
|
4308
|
-
sync_messages(headless=args.headless)
|
|
4450
|
+
sync_messages(headless=args.headless, full_history=args.full_history)
|
|
4309
4451
|
elif args.search:
|
|
4310
4452
|
scrape_full(headless=args.headless)
|
|
4311
4453
|
elif args.full:
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
module.exports=[70406,(e,r,t)=>{r.exports=e.x("next/dist/compiled/@opentelemetry/api",()=>require("next/dist/compiled/@opentelemetry/api"))},18622,(e,r,t)=>{r.exports=e.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js",()=>require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"))},56704,(e,r,t)=>{r.exports=e.x("next/dist/server/app-render/work-async-storage.external.js",()=>require("next/dist/server/app-render/work-async-storage.external.js"))},32319,(e,r,t)=>{r.exports=e.x("next/dist/server/app-render/work-unit-async-storage.external.js",()=>require("next/dist/server/app-render/work-unit-async-storage.external.js"))},59043,(e,r,t)=>{r.exports=e.x("next/dist/server/runtime-reacts.external.js",()=>require("next/dist/server/runtime-reacts.external.js"))},93695,(e,r,t)=>{r.exports=e.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},74533,(e,r,t)=>{r.exports=e.x("node:child_process",()=>require("node:child_process"))},66680,(e,r,t)=>{r.exports=e.x("node:crypto",()=>require("node:crypto"))},2157,(e,r,t)=>{r.exports=e.x("node:fs",()=>require("node:fs"))},12714,(e,r,t)=>{r.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},60526,(e,r,t)=>{r.exports=e.x("node:os",()=>require("node:os"))},50227,(e,r,t)=>{r.exports=e.x("node:path",()=>require("node:path"))},81111,(e,r,t)=>{r.exports=e.x("node:stream",()=>require("node:stream"))},93836,19307,e=>{"use strict";e.i(68835);var r=e.i(1746);let t=e=>String(e||"").split(",")[0].normalize("NFD").replace(/[̀-ͯ]/g,"").toLowerCase().replace(/[^a-z0-9 ]+/g," ").replace(/\s+/g," ").trim(),n=/^https:\/\/www\.linkedin\.com\/messaging\/[\w\-/%=.]*$/;e.s(["conversationList",0,function(e=[],r={}){let t=(Array.isArray(e)?e:[]).map(e=>({...e,unread:null,threadUrl:null})),n=new Map;for(let e of t){if(e.group||e.people?.length!==1)continue;let r=n.get(e.people[0]);(!r||(e.last||0)>(r.last||0))&&n.set(e.people[0],e)}for(let[e,s]of Object.entries(r||{})){if(!s||"object"!=typeof s)continue;let r=n.get(e);r||(r={id:`live:${e}`,people:[e],group:!1,last:null,lastFromThem:null,count:null,unread:null,threadUrl:null},t.push(r),n.set(e,r)),s.last&&(!r.last||s.last>r.last)&&(r.last=s.last,"boolean"==typeof s.lastFromThem&&(r.lastFromThem=s.lastFromThem)),r.unread=Number.isFinite(s.unread)?s.unread:r.unread,r.threadUrl=s.threadUrl||r.threadUrl}return t},"matchLive",0,function(e={},s=[]){let i=new Map,l=new Map;for(let e of s){let n=(0,r.keyFor)(e);i.set(n,n);let s=t(e.name);s&&l.set(s,l.has(s)?null:n)}let o={},a=0;for(let[s,u]of Object.entries(e||{})){if(!u||"object"!=typeof u)continue;let e=i.get((0,r.keyFor)({profile_url:s}))||l.get(t(u.name))||null;if(!e){a+=1;continue}let c=Number(u.last)||null;o[e]&&(o[e].last||0)>=(c||0)||(o[e]={last:c,unread:Number.isFinite(Number(u.unread))&&null!==u.unread?Number(u.unread):null,threadUrl:function(e){let r=String(e||"").trim();return n.test(r)?r:null}(u.threadUrl),lastFromThem:"boolean"==typeof u.lastFromThem?u.lastFromThem:null,preview:function(e){if(!e||"object"!=typeof e)return null;let r=Number(e.t)||null,t="string"==typeof e.text?e.text.slice(0,2e4):null;return r||t?{t:r,fromMe:!0===e.fromMe,text:t}:null}(u.preview)})}return{live:o,unmatched:a}}],93836);var s=e.i(2157),i=e.i(50227),l=e.i(88431),o=e.i(88602);let a={file:null,mtimeMs:0,size:0,data:null};function u(e,r=null){try{return JSON.parse((0,s.readFileSync)(e,"utf8"))??r}catch{return r}}e.s(["deleteMessages",0,function(e){e&&((0,s.rmSync)(e.messages,{force:!0}),(0,s.rmSync)(e.incoming,{force:!0}),a.file=null)},"readJson",0,u,"readMessages",0,function(e){let r;try{r=(0,s.statSync)(e)}catch{return{threads:{},previews:{}}}if(a.file===e&&a.mtimeMs===r.mtimeMs&&a.size===r.size)return a.data;let t=u(e,{}),n={threads:t?.threads&&"object"==typeof t.threads?t.threads:{},previews:t?.previews&&"object"==typeof t.previews?t.previews:{}};return Object.assign(a,{file:e,mtimeMs:r.mtimeMs,size:r.size,data:n}),n},"socialFiles",0,function(){let e=(0,o.resolveProfile)({create:!1});if(!e?.id||!/^[\w-]+$/.test(String(e.id)))return null;let r=(0,l.dataDir)(),t=i.default.join(r,`social-messages-${e.id}.json`);return{profileId:e.id,social:i.default.join(r,`social-${e.id}.json`),messages:t,incoming:`${t}.incoming`}},"writeJsonAtomic",0,function(e,r){let t=`${e}.${process.pid}.tmp`;(0,s.writeFileSync)(t,JSON.stringify(r)),(0,s.renameSync)(t,e),a.file===e&&(a.file=null)}],19307)},88602,e=>{"use strict";var r=e.i(88431);e.s(["networkCounts",0,function(e){let t=Object.fromEntries((0,r.getDb)().prepare("SELECT degree, COUNT(*) AS n FROM linkedin_connections WHERE user_id = ? GROUP BY degree").all(e).map(e=>[e.degree,e.n]));return{first:t[1]||0,second:t[2]||0,third:t[3]||0}},"resolveProfile",0,function({create:e=!0}={}){let[t]=(0,r.getDb)().prepare(`
|
|
2
|
-
SELECT u.*,
|
|
3
|
-
(SELECT COUNT(*) FROM linkedin_connections c WHERE c.user_id = u.id) AS connections
|
|
4
|
-
FROM users u
|
|
5
|
-
ORDER BY connections DESC, u.created_at ASC, u.rowid ASC
|
|
6
|
-
`).all().map(e=>(0,r.decodeRow)("users",e));if(t)return t;if(!e)return null;let n=(0,r.newId)();return(0,r.getDb)().prepare("INSERT INTO users (id, name) VALUES (?, ?)").run(n,"You"),{...(0,r.decodeRow)("users",(0,r.getDb)().prepare("SELECT * FROM users WHERE id = ?").get(n)),connections:0}}])},12506,1348,e=>{"use strict";e.s(["compareVersions",0,function(e,r){let t=e=>String(e||"").replace(/^v/i,"").split(/[-+]/)[0].split(".").map(e=>Number.parseInt(e,10)||0),n=t(e),s=t(r);for(let e=0;e<Math.max(n.length,s.length);e++){let r=(n[e]||0)-(s[e]||0);if(0!==r)return r>0?1:-1}return 0},"installKind",0,function(e=process.env,r=""){return["mac-app","source","npm"].includes(e.SIX_DEGREES_INSTALL)?e.SIX_DEGREES_INSTALL:/\.app\/Contents\/Resources/.test(r||"")?"mac-app":"npm"},"repoSlug",0,function(e){let r=("string"==typeof e?e:e?.url||"").match(/github\.com[/:]([^/]+)\/([^/.#?]+)/);return r?`${r[1]}/${r[2]}`:null},"updateCommand",0,function(e,r,{dataDir:t}={}){if("mac-app"===e)return`curl -fsSL https://raw.githubusercontent.com/${r}/main/install.sh | bash`;if("source"===e)return"git pull && npm ci && npm run build && npm run start:packaged";let n=t&&`'${String(t).replace(/'/g,"'\\''")}'`;return n?`npx six-degrees@latest --data-dir ${n}`:"npx six-degrees@latest"}],12506);let r=Symbol.for("six-degrees.scan-state");function t(){let e=globalThis[r];return e?.running!==!0?null:"string"==typeof e.action&&e.action?e.action:"scan"}function n(e){switch(e){case"install":return"The scanner's packages are being installed";case"setup":return"The scanner is being set up";case"login":return"The LinkedIn sign-in window is open";case"photos":return"Profile photos are being saved";default:return"A scan is running"}}e.s(["busyRefusal",0,function(e,r=t()){return r?`${n(r)}. ${e}`:null},"registerScanState",0,function(e){return globalThis[r]||(globalThis[r]=e),globalThis[r]},"runningNow",0,n,"scannerJob",0,t],1348)},1746,e=>{"use strict";e.s(["keyFor",0,function(e){return String(e?.profile_url||"").split(/[?#]/)[0].replace(/\/+$/,"")||`id:${e?.id}`}])}];
|
|
7
|
-
|
|
8
|
-
//# sourceMappingURL=%5Broot-of-the-server%5D__08f5awg._.js.map
|