six-degrees 0.4.7 → 0.4.9
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 +3 -3
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- 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 +2 -2
- 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 +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +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.nft.json +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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +2 -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 +2 -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.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.nft.json +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.nft.json +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.nft.json +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 +2 -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 +3 -2
- 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 +2 -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 +2 -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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/messages/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +9 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/messages/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/route.js +4 -2
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +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.nft.json +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/favicon.ico/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/favicon.ico.body +0 -0
- 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 +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
- 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 +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06mi9k2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +6 -6
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_messages_route_actions_0skh09r.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +1 -1
- package/.next/standalone/.next/server/chunks/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 +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00d8snt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00g61b8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08lf3gy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ho-u0w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1feicqq._.js → [root-of-the-server]__0qdeuyo._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ze473p._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1cnxxc9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ll97ry._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1w9z0zv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mj739k._.js → _0l8cxmo._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0wxbq78._.js → app_components_19c2d9c._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
- 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 +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +1 -1
- 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 +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_1wax83z._.js → node_modules_0h91jdk._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_1nkgb45._.js → node_modules_1f8t_rp._.js} +1 -1
- 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/{2_zu7rmb8flzl.js → 04bbi55urkrd6.js} +1 -1
- package/.next/{static/chunks/3q3pmt5vv-4pi.js → standalone/.next/static/chunks/054zdthcnyd2h.js} +1 -1
- package/.next/{static/chunks/236f0jtp68j_h.js → standalone/.next/static/chunks/1_mohm2643nxp.js} +1 -1
- package/.next/{static/chunks/3zw8bv7-mc4n6.js → standalone/.next/static/chunks/1z_rt4jteewqp.js} +1 -1
- package/.next/standalone/.next/static/chunks/24tvdun1cb3g1.js +1 -0
- package/.next/standalone/.next/static/chunks/2bftn9gubwf0w.js +1 -0
- package/.next/{static/chunks/0g_p4p7oci18u.js → standalone/.next/static/chunks/2kfhc58df2xyo.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +1 -0
- package/.next/standalone/.next/static/chunks/39csdcx7yv227.js +1 -0
- package/.next/standalone/.next/static/chunks/3yzlf70j5t0-o.js +1 -0
- package/.next/standalone/.next/static/chunks/{2_wcobfqry2uj.js → 3z-swu5pet507.js} +1 -1
- package/.next/standalone/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
- package/.next/standalone/CHANGELOG.md +72 -0
- package/.next/standalone/CONTRIBUTING.md +5 -0
- package/.next/standalone/README.md +168 -56
- package/.next/standalone/app/api/social/messages/route.js +130 -0
- package/.next/standalone/app/api/social/route.js +89 -47
- package/.next/standalone/app/components/ScanStatusBar.js +40 -3
- package/.next/standalone/app/components/settings/DataSection.js +2 -1
- package/.next/standalone/app/favicon.ico +0 -0
- package/.next/standalone/app/paths/page.js +27 -18
- package/.next/standalone/app/profile/page.js +2 -0
- package/.next/standalone/app/queue/page.js +2 -0
- package/.next/standalone/app/scores/page.js +2 -0
- package/.next/standalone/app/settings/page.js +2 -0
- package/.next/standalone/app/setup/page.js +6 -3
- package/.next/standalone/app/social/Conversations.js +246 -0
- package/.next/standalone/app/social/page.js +90 -9
- package/.next/standalone/lib/galaxy-lab.js +6 -1
- package/.next/standalone/lib/linkedin-export.js +175 -5
- package/.next/standalone/lib/scoring.js +42 -1
- package/.next/standalone/lib/social-store.js +73 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/build-site.mjs +537 -0
- package/.next/standalone/scripts/scrape.py +156 -23
- package/.next/standalone/scripts/site-markdown.mjs +188 -0
- package/.next/standalone/scripts/site-news.mjs +311 -0
- package/.next/static/chunks/{2_zu7rmb8flzl.js → 04bbi55urkrd6.js} +1 -1
- package/.next/{standalone/.next/static/chunks/3q3pmt5vv-4pi.js → static/chunks/054zdthcnyd2h.js} +1 -1
- package/.next/{standalone/.next/static/chunks/236f0jtp68j_h.js → static/chunks/1_mohm2643nxp.js} +1 -1
- package/.next/{standalone/.next/static/chunks/3zw8bv7-mc4n6.js → static/chunks/1z_rt4jteewqp.js} +1 -1
- package/.next/static/chunks/24tvdun1cb3g1.js +1 -0
- package/.next/static/chunks/2bftn9gubwf0w.js +1 -0
- package/.next/{standalone/.next/static/chunks/0g_p4p7oci18u.js → static/chunks/2kfhc58df2xyo.js} +1 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +1 -0
- package/.next/static/chunks/39csdcx7yv227.js +1 -0
- package/.next/static/chunks/3yzlf70j5t0-o.js +1 -0
- package/.next/static/chunks/{2_wcobfqry2uj.js → 3z-swu5pet507.js} +1 -1
- package/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
- package/CHANGELOG.md +72 -0
- package/README.md +168 -56
- package/lib/galaxy-lab.js +6 -1
- package/lib/linkedin-export.js +175 -5
- package/lib/scoring.js +42 -1
- package/lib/social-store.js +73 -0
- package/package.json +1 -1
- package/scripts/scrape.py +156 -23
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17kn1mk._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dmc7-t._.js +0 -8
- package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_217nddo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_0i3fw_m._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +0 -3
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/0y1yms5kez2f3.js +0 -1
- package/.next/standalone/.next/static/chunks/14m1zu-8warte.js +0 -1
- package/.next/standalone/.next/static/chunks/15qbli9udo0vh.js +0 -1
- package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
- package/.next/standalone/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/0y1yms5kez2f3.js +0 -1
- package/.next/static/chunks/14m1zu-8warte.js +0 -1
- package/.next/static/chunks/15qbli9udo0vh.js +0 -1
- package/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
- package/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
- /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_ssgManifest.js +0 -0
- /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_buildManifest.js +0 -0
- /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_ssgManifest.js +0 -0
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
//
|
|
7
7
|
// people per profile: when you last messaged, who wrote last, how many
|
|
8
8
|
// messages (1:1 conversations only). Never a message's text.
|
|
9
|
+
// conversations per conversation: who's in it, when it was last active, who
|
|
10
|
+
// wrote last, how many messages. The text itself only when you turn
|
|
11
|
+
// on Keep my messages, and then in a file of its own
|
|
12
|
+
// (app/api/social/messages).
|
|
9
13
|
// chapters your jobs (Positions.csv) and the connections made in each
|
|
10
14
|
// posts your posts (Shares.csv) and the connections that came the week after
|
|
11
15
|
// invites requests to and from you (Invitations.csv), when the export has them
|
|
@@ -49,14 +53,29 @@ export function exportDate(s) {
|
|
|
49
53
|
|
|
50
54
|
const urlKey = (u) => keyFor({ profile_url: u });
|
|
51
55
|
|
|
56
|
+
/** You, in messages.csv: the sender in the most conversations. */
|
|
57
|
+
function selfOf(rows = []) {
|
|
58
|
+
const senders = new Map();
|
|
59
|
+
for (const r of rows) {
|
|
60
|
+
const from = urlKey(r['sender profile url']);
|
|
61
|
+
const id = r['conversation id'];
|
|
62
|
+
if (!id || !from) continue;
|
|
63
|
+
if (!senders.has(from)) senders.set(from, new Set());
|
|
64
|
+
senders.get(from).add(id);
|
|
65
|
+
}
|
|
66
|
+
let self = null;
|
|
67
|
+
for (const [who, ids] of senders) if (!self || ids.size > senders.get(self).size) self = who;
|
|
68
|
+
return self;
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
/**
|
|
53
72
|
* From messages.csv, per person (by profile): { last, lastFromThem, total, recent }.
|
|
54
73
|
* `self` is you: the sender in the most conversations. Only 1:1 conversations
|
|
55
74
|
* count; group threads say little about one relationship. No text is read.
|
|
56
75
|
*/
|
|
57
76
|
export function messageStats(rows = []) {
|
|
77
|
+
const self = selfOf(rows);
|
|
58
78
|
const convos = new Map();
|
|
59
|
-
const senders = new Map();
|
|
60
79
|
for (const r of rows) {
|
|
61
80
|
const from = urlKey(r['sender profile url']);
|
|
62
81
|
const id = r['conversation id'];
|
|
@@ -69,11 +88,7 @@ export function messageStats(rows = []) {
|
|
|
69
88
|
c.people.add(from);
|
|
70
89
|
to.forEach((p) => c.people.add(p));
|
|
71
90
|
c.msgs.push({ from, t });
|
|
72
|
-
if (!senders.has(from)) senders.set(from, new Set());
|
|
73
|
-
senders.get(from).add(id);
|
|
74
91
|
}
|
|
75
|
-
let self = null;
|
|
76
|
-
for (const [who, ids] of senders) if (!self || ids.size > senders.get(self).size) self = who;
|
|
77
92
|
let asOf = 0;
|
|
78
93
|
const people = new Map();
|
|
79
94
|
for (const c of convos.values()) {
|
|
@@ -96,6 +111,104 @@ export function messageStats(rows = []) {
|
|
|
96
111
|
return { self, asOf: asOf || null, people };
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Every conversation in messages.csv you're part of, one record each:
|
|
116
|
+
* { id, title, people, group, messages: [{ t, fromMe, text }] }, messages oldest
|
|
117
|
+
* first. `people` is everyone but you, by profile key; more than one makes it a
|
|
118
|
+
* group. You are found the way messageStats finds you. The words (the subject,
|
|
119
|
+
* when there is one, then the message) are read only with keepText: without it
|
|
120
|
+
* a message is a date and who sent it, and the title (a group's name) is left
|
|
121
|
+
* out too.
|
|
122
|
+
*/
|
|
123
|
+
export function buildConversations(rows = [], { keepText = false } = {}) {
|
|
124
|
+
const self = selfOf(rows);
|
|
125
|
+
const convos = new Map();
|
|
126
|
+
for (const r of rows) {
|
|
127
|
+
const from = urlKey(r['sender profile url']);
|
|
128
|
+
const id = r['conversation id'];
|
|
129
|
+
if (!id || !from) continue;
|
|
130
|
+
const t = exportDate(r.date);
|
|
131
|
+
if (t == null) continue;
|
|
132
|
+
if (!convos.has(id)) convos.set(id, { id, title: '', everyone: new Set(), messages: [] });
|
|
133
|
+
const c = convos.get(id);
|
|
134
|
+
c.everyone.add(from);
|
|
135
|
+
String(r['recipient profile urls'] || '').split(/[,;\s]+/).filter(Boolean).forEach((u) => c.everyone.add(urlKey(u)));
|
|
136
|
+
const m = { t, fromMe: from === self };
|
|
137
|
+
if (keepText) {
|
|
138
|
+
m.text = [r.subject, r.content].map((x) => String(x || '').trim()).filter(Boolean).join('\n\n');
|
|
139
|
+
if (!c.title && r['conversation title']) c.title = String(r['conversation title']).trim();
|
|
140
|
+
}
|
|
141
|
+
c.messages.push(m);
|
|
142
|
+
}
|
|
143
|
+
const conversations = [];
|
|
144
|
+
for (const c of convos.values()) {
|
|
145
|
+
if (!c.everyone.has(self)) continue;
|
|
146
|
+
const people = [...c.everyone].filter((p) => p && p !== self).sort();
|
|
147
|
+
if (!people.length) continue;
|
|
148
|
+
c.messages.sort((a, b) => a.t - b.t);
|
|
149
|
+
const out = { id: c.id, people, group: people.length > 1, messages: c.messages };
|
|
150
|
+
if (keepText) out.title = c.title;
|
|
151
|
+
conversations.push(out);
|
|
152
|
+
}
|
|
153
|
+
return { self, conversations };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** What the Conversations list keeps of one conversation, never any words: { id, people, group, last, lastFromThem, count }. */
|
|
157
|
+
export function conversationSummary(c) {
|
|
158
|
+
const last = c.messages[c.messages.length - 1];
|
|
159
|
+
return {
|
|
160
|
+
id: c.id,
|
|
161
|
+
people: c.people,
|
|
162
|
+
group: c.group,
|
|
163
|
+
last: last ? last.t : null,
|
|
164
|
+
lastFromThem: last ? !last.fromMe : null,
|
|
165
|
+
count: c.messages.length,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The Conversations list: the export's conversations with what the live sync
|
|
171
|
+
* adds (unread, the thread's link, and a newer last message), plus a
|
|
172
|
+
* conversation for each person the sync found and the export doesn't have yet
|
|
173
|
+
* (id `live:<key>`, count unknown).
|
|
174
|
+
*/
|
|
175
|
+
export function conversationList(list = [], live = {}) {
|
|
176
|
+
const out = (Array.isArray(list) ? list : []).map((c) => ({ ...c, unread: null, threadUrl: null }));
|
|
177
|
+
const oneToOne = new Map();
|
|
178
|
+
for (const c of out) {
|
|
179
|
+
if (c.group || c.people?.length !== 1) continue;
|
|
180
|
+
const had = oneToOne.get(c.people[0]);
|
|
181
|
+
if (!had || (c.last || 0) > (had.last || 0)) oneToOne.set(c.people[0], c);
|
|
182
|
+
}
|
|
183
|
+
for (const [key, v] of Object.entries(live || {})) {
|
|
184
|
+
if (!v || typeof v !== 'object') continue;
|
|
185
|
+
let c = oneToOne.get(key);
|
|
186
|
+
if (!c) {
|
|
187
|
+
c = { id: `live:${key}`, people: [key], group: false, last: null, lastFromThem: null, count: null, unread: null, threadUrl: null };
|
|
188
|
+
out.push(c);
|
|
189
|
+
oneToOne.set(key, c);
|
|
190
|
+
}
|
|
191
|
+
if (v.last && (!c.last || v.last > c.last)) {
|
|
192
|
+
c.last = v.last;
|
|
193
|
+
if (typeof v.lastFromThem === 'boolean') c.lastFromThem = v.lastFromThem;
|
|
194
|
+
}
|
|
195
|
+
c.unread = Number.isFinite(v.unread) ? v.unread : c.unread;
|
|
196
|
+
c.threadUrl = v.threadUrl || c.threadUrl;
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The order the Conversations list shows: waiting on you (they wrote last, one
|
|
203
|
+
* to one) first, then your S and A tier people, then the most recent.
|
|
204
|
+
* `tierOf(conversation)` gives its best tier.
|
|
205
|
+
*/
|
|
206
|
+
export function sortConversations(list = [], tierOf = () => null) {
|
|
207
|
+
const waiting = (c) => (!c.group && c.lastFromThem === true ? 0 : 1);
|
|
208
|
+
const tier = (c) => ({ S: 0, A: 1 }[tierOf(c)] ?? 2);
|
|
209
|
+
return [...list].sort((a, b) => waiting(a) - waiting(b) || tier(a) - tier(b) || (b.last || 0) - (a.last || 0));
|
|
210
|
+
}
|
|
211
|
+
|
|
99
212
|
/** Warm (in the last 30 days, or 3+ in 90), cool (within a year), dormant (longer ago), or never. */
|
|
100
213
|
export function warmthOf(stat, asOf) {
|
|
101
214
|
if (!stat || !stat.total) return 'never';
|
|
@@ -154,3 +267,60 @@ export function invitationSplit(rows = []) {
|
|
|
154
267
|
}
|
|
155
268
|
return { incoming, outgoing, total: incoming + outgoing };
|
|
156
269
|
}
|
|
270
|
+
|
|
271
|
+
const nameKey = (s) => String(s || '').split(',')[0].normalize('NFD').replace(/[̀-ͯ]/g, '')
|
|
272
|
+
.toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
273
|
+
|
|
274
|
+
const THREAD_LINK = /^https:\/\/www\.linkedin\.com\/messaging\/[\w\-/%=.]*$/;
|
|
275
|
+
|
|
276
|
+
/** A link to a LinkedIn messaging thread, or null: nothing else is ever put behind "Open on LinkedIn". */
|
|
277
|
+
export function threadLink(u) {
|
|
278
|
+
const v = String(u || '').trim();
|
|
279
|
+
return THREAD_LINK.test(v) ? v : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A message the live sync read, as it's kept: { t, fromMe, text }, or null. */
|
|
283
|
+
function previewOf(p) {
|
|
284
|
+
if (!p || typeof p !== 'object') return null;
|
|
285
|
+
const t = Number(p.t) || null;
|
|
286
|
+
const text = typeof p.text === 'string' ? p.text.slice(0, 20000) : null;
|
|
287
|
+
if (!t && !text) return null;
|
|
288
|
+
return { t, fromMe: p.fromMe === true, text };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The live messages sync's people, matched to your connections. LinkedIn's
|
|
293
|
+
* messaging gives a member link (/in/ACoA…) your connections list doesn't have,
|
|
294
|
+
* so a person is matched by their link when it's the same, otherwise by their
|
|
295
|
+
* name when exactly one connection has it. Only the match is kept, never the
|
|
296
|
+
* name. Returns { live: { connectionKey: { last, unread, threadUrl,
|
|
297
|
+
* lastFromThem, preview } }, unmatched }; the caller decides whether the
|
|
298
|
+
* preview's words are kept.
|
|
299
|
+
*/
|
|
300
|
+
export function matchLive(incoming = {}, connections = []) {
|
|
301
|
+
const byUrl = new Map();
|
|
302
|
+
const byName = new Map();
|
|
303
|
+
for (const c of connections) {
|
|
304
|
+
const key = keyFor(c);
|
|
305
|
+
byUrl.set(key, key);
|
|
306
|
+
const n = nameKey(c.name);
|
|
307
|
+
if (n) byName.set(n, byName.has(n) ? null : key); // null: two people share it
|
|
308
|
+
}
|
|
309
|
+
const live = {};
|
|
310
|
+
let unmatched = 0;
|
|
311
|
+
for (const [url, v] of Object.entries(incoming || {})) {
|
|
312
|
+
if (!v || typeof v !== 'object') continue;
|
|
313
|
+
const key = byUrl.get(keyFor({ profile_url: url })) || byName.get(nameKey(v.name)) || null;
|
|
314
|
+
if (!key) { unmatched += 1; continue; }
|
|
315
|
+
const last = Number(v.last) || null;
|
|
316
|
+
if (live[key] && (live[key].last || 0) >= (last || 0)) continue;
|
|
317
|
+
live[key] = {
|
|
318
|
+
last,
|
|
319
|
+
unread: Number.isFinite(Number(v.unread)) && v.unread !== null ? Number(v.unread) : null,
|
|
320
|
+
threadUrl: threadLink(v.threadUrl),
|
|
321
|
+
lastFromThem: typeof v.lastFromThem === 'boolean' ? v.lastFromThem : null,
|
|
322
|
+
preview: previewOf(v.preview),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return { live, unmatched };
|
|
326
|
+
}
|
|
@@ -58,6 +58,31 @@
|
|
|
58
58
|
// themselves are tables, fingerprinted below.)
|
|
59
59
|
export const SCORING_VERSION = 6;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A function of one piece of text, remembered: the same text gets the answer
|
|
63
|
+
* it got the first time, without the work again. Reading a headline or a
|
|
64
|
+
* company name runs hundreds of patterns (every name on the curated list and
|
|
65
|
+
* the public dataset is tried against it), and a network hands over the same
|
|
66
|
+
* text again and again: everyone at a company writes its name, and a page
|
|
67
|
+
* groups, links and filters the same people several times over. At 30,000
|
|
68
|
+
* people each of those passes took seconds, and Paths 10 to 20 to open.
|
|
69
|
+
*
|
|
70
|
+
* Only for a function whose answer depends on nothing but the text, and whose
|
|
71
|
+
* answer nobody changes afterwards (readRoles hands out copies for that). It
|
|
72
|
+
* keeps up to `max` answers and drops the oldest first, so it can't grow
|
|
73
|
+
* without end on a server that stays up for weeks.
|
|
74
|
+
*/
|
|
75
|
+
export function rememberByText(read, max = 100000) {
|
|
76
|
+
const answers = new Map();
|
|
77
|
+
return (text) => {
|
|
78
|
+
if (answers.has(text)) return answers.get(text);
|
|
79
|
+
const answer = read(text);
|
|
80
|
+
if (answers.size >= max) answers.delete(answers.keys().next().value);
|
|
81
|
+
answers.set(text, answer);
|
|
82
|
+
return answer;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
// ── title ────────────────────────────────────────────────────────────────────
|
|
62
87
|
|
|
63
88
|
export const LEVELS = {
|
|
@@ -1008,8 +1033,16 @@ function heldAfterComma(part) {
|
|
|
1008
1033
|
* An audience of their own is a role of its own (`audience`), with no company.
|
|
1009
1034
|
*/
|
|
1010
1035
|
export function readRoles(headline = '', role = '') {
|
|
1036
|
+
// Copies, since rolesWithCompanies fills in a role's company on what it gets.
|
|
1037
|
+
return rolesIn(String(headline || role || '')).map((r) => ({ ...r, title: { ...r.title } }));
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Only the text is read (the headline, else the role), so each is read once.
|
|
1041
|
+
const rolesIn = rememberByText(readRolesOf);
|
|
1042
|
+
|
|
1043
|
+
function readRolesOf(text) {
|
|
1011
1044
|
const out = [];
|
|
1012
|
-
const parts = headlineParts(
|
|
1045
|
+
const parts = headlineParts(text);
|
|
1013
1046
|
let last = null; // the last role a title gave: { role, i, rank }
|
|
1014
1047
|
let retiredNext = false; // a lone "Retired" before any role: the next one's
|
|
1015
1048
|
for (const [i, raw] of parts.entries()) {
|
|
@@ -2033,6 +2066,14 @@ const PLATFORMS = new Set(['tech', 'media', 'entertainment']);
|
|
|
2033
2066
|
|
|
2034
2067
|
/** A company name as people write it → one clean, canonical name (or null). */
|
|
2035
2068
|
export function cleanCompany(raw) {
|
|
2069
|
+
return cleanNameOf(String(raw || ''));
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// Read once per name: everyone at a company writes it, and each read tries the
|
|
2073
|
+
// whole curated list and public dataset against it.
|
|
2074
|
+
const cleanNameOf = rememberByText(readCompanyName);
|
|
2075
|
+
|
|
2076
|
+
function readCompanyName(raw) {
|
|
2036
2077
|
// NFKC first: styled letters ("𝗠𝗶𝗰𝗿𝗼𝘀𝗼𝗳𝘁") and full-width punctuation ("|")
|
|
2037
2078
|
// become plain ones. Private-use glyphs go with the emoji: the Apple logo
|
|
2038
2079
|
// () after "Apple" used to leave it unread.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Where the Social tab keeps what it knows, per profile, in the data folder:
|
|
2
|
+
//
|
|
3
|
+
// social-<profile>.json numbers and dates (app/api/social): who you
|
|
4
|
+
// messaged, when, who wrote last, the list of
|
|
5
|
+
// conversations. Never any words.
|
|
6
|
+
// social-messages-<profile>.json the messages themselves, only while Keep my
|
|
7
|
+
// messages is on (app/api/social/messages).
|
|
8
|
+
//
|
|
9
|
+
// Two files so that turning Keep my messages off, or Forget it, deletes the
|
|
10
|
+
// 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. Neither is in
|
|
12
|
+
// "Save a copy of my network": that carries an allow-list of files
|
|
13
|
+
// (lib/data-folder.js TRAVELLING_FILES) and these aren't on it.
|
|
14
|
+
|
|
15
|
+
import { readFileSync, writeFileSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { dataDir } from './db-client';
|
|
18
|
+
import { resolveProfile } from './profile';
|
|
19
|
+
|
|
20
|
+
// The last messages file read, while it's unchanged (readMessages).
|
|
21
|
+
const cache = { file: null, mtimeMs: 0, size: 0, data: null };
|
|
22
|
+
|
|
23
|
+
/** The Social tab's files for the profile in use, or null before there is one. */
|
|
24
|
+
export function socialFiles() {
|
|
25
|
+
const me = resolveProfile({ create: false });
|
|
26
|
+
if (!me?.id || !/^[\w-]+$/.test(String(me.id))) return null;
|
|
27
|
+
const dir = dataDir();
|
|
28
|
+
const messages = path.join(dir, `social-messages-${me.id}.json`);
|
|
29
|
+
return {
|
|
30
|
+
profileId: me.id,
|
|
31
|
+
social: path.join(dir, `social-${me.id}.json`),
|
|
32
|
+
messages,
|
|
33
|
+
// An import's messages arrive in parts (each request stays under the 10 MB
|
|
34
|
+
// middleware reads); they gather here and replace `messages` at the end.
|
|
35
|
+
incoming: `${messages}.incoming`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readJson(file, fallback = null) {
|
|
40
|
+
try { return JSON.parse(readFileSync(file, 'utf8')) ?? fallback; } catch { return fallback; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Written whole or not at all: a crash mid-write never leaves half a file. */
|
|
44
|
+
export function writeJsonAtomic(file, value) {
|
|
45
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
46
|
+
writeFileSync(tmp, JSON.stringify(value));
|
|
47
|
+
renameSync(tmp, file);
|
|
48
|
+
if (cache.file === file) cache.file = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The kept messages and their live-sync previews, or empty ones. */
|
|
52
|
+
export function readMessages(file) {
|
|
53
|
+
// Kept in memory while the file is unchanged: a long history is read once,
|
|
54
|
+
// not on every page of a thread or every letter typed in the search box.
|
|
55
|
+
let st;
|
|
56
|
+
try { st = statSync(file); } catch { return { threads: {}, previews: {} }; }
|
|
57
|
+
if (cache.file === file && cache.mtimeMs === st.mtimeMs && cache.size === st.size) return cache.data;
|
|
58
|
+
const raw = readJson(file, {});
|
|
59
|
+
const data = {
|
|
60
|
+
threads: raw?.threads && typeof raw.threads === 'object' ? raw.threads : {},
|
|
61
|
+
previews: raw?.previews && typeof raw.previews === 'object' ? raw.previews : {},
|
|
62
|
+
};
|
|
63
|
+
Object.assign(cache, { file, mtimeMs: st.mtimeMs, size: st.size, data });
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The messages gone: the kept file and any import still arriving. */
|
|
68
|
+
export function deleteMessages(files) {
|
|
69
|
+
if (!files) return;
|
|
70
|
+
rmSync(files.messages, { force: true });
|
|
71
|
+
rmSync(files.incoming, { force: true });
|
|
72
|
+
cache.file = null;
|
|
73
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "six-degrees",
|
|
9
|
-
"version": "0.4.
|
|
9
|
+
"version": "0.4.9",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"os": [
|
|
12
12
|
"darwin",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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",
|