birdclaw 0.8.3 → 0.8.4
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/CHANGELOG.md +17 -0
- package/README.md +10 -8
- package/bin/birdclaw.mjs +4 -66
- package/dist/cli/birdclaw.js +28165 -0
- package/dist/client/assets/AvatarChip-DLoqM5xD.js +1 -0
- package/dist/client/assets/ProfilePreview-BejAuYsJ.js +1 -0
- package/dist/client/assets/SavedTimelineView-BIS7HKpp.js +1 -0
- package/dist/client/assets/SmartTimestamp-BR0-9se2.js +1 -0
- package/dist/client/assets/TimelineRouteFrame-CyGPj0eM.js +1 -0
- package/dist/client/assets/blocks-7UDKQp8H.js +1 -0
- package/dist/client/assets/bookmarks-CbBkMvXk.js +1 -0
- package/dist/client/assets/chunk-C0phanGV.js +1 -0
- package/dist/client/assets/circle-check-C_Vq3ElD.js +1 -0
- package/dist/client/assets/client-stream-contracts-ys3XhWG4.js +5 -0
- package/dist/client/assets/createLucideIcon-JV8isFFR.js +1 -0
- package/dist/client/assets/data-sources-Cm2bpNvg.js +1 -0
- package/dist/client/assets/discuss-DTjaOugw.js +1 -0
- package/dist/client/assets/dms-CGd6Eogq.js +1 -0
- package/dist/client/assets/inbox-BHlGuOy2.js +1 -0
- package/dist/client/assets/index-CEIG7S6s.js +31 -0
- package/dist/client/assets/index-bpFrQ5yG.css +1 -0
- package/dist/client/assets/likes-cLrcAQem.js +1 -0
- package/dist/client/assets/links-CM9QO1LC.js +1 -0
- package/dist/client/assets/mapbox-BxTlqXt9.js +2 -0
- package/dist/client/assets/mapbox-gl-dgf_hX3a.js +3215 -0
- package/dist/client/assets/mentions-CxKCgzIf.js +1 -0
- package/dist/client/assets/network-map-B5a3whqj.js +2 -0
- package/dist/client/assets/profile-analyze-CRtNEwlb.js +1 -0
- package/dist/client/assets/profile-hydration-client-YEtJsNTv.js +1 -0
- package/dist/client/assets/profiles._handle-C82_HoKX.js +1 -0
- package/dist/client/assets/query-client-fYu2rzRC.js +17 -0
- package/dist/client/assets/rate-limits-ScaEwdVK.js +1 -0
- package/dist/client/assets/react-dom-DHl6Ug7F.js +1 -0
- package/dist/client/assets/react-nBylq1U5.js +1 -0
- package/dist/client/assets/repeat-2-xCwHM9hD.js +1 -0
- package/dist/client/assets/routes-q7ZaW4En.js +1 -0
- package/dist/client/assets/schemas-I9r6F6ya.js +64 -0
- package/dist/client/assets/search-Dq5kYsxA.js +1 -0
- package/dist/client/assets/sparkles-CQVlvHsZ.js +1 -0
- package/dist/client/assets/styles-B8s3tt3r.css +2 -0
- package/dist/client/assets/today-CeG1O7Py.js +1 -0
- package/dist/client/assets/triangle-alert-CBL7s2pf.js +1 -0
- package/dist/client/assets/useTimelineRouteData-1HsqgvUi.js +1 -0
- package/dist/client/assets/users-CHbKlqUr.js +1 -0
- package/dist/server/assets/AvatarChip-zUDAEmjL.js +105 -0
- package/dist/server/assets/FeedState-ntW0Q2GB.js +214 -0
- package/dist/server/assets/ProfileAnalysisStream-BFvhVdhn.js +198 -0
- package/dist/server/assets/ProfilePreview-C6gAor_j.js +752 -0
- package/dist/server/assets/SavedTimelineView-BG1jnBp1.js +64 -0
- package/dist/server/assets/SmartTimestamp-DfwE1NGf.js +45 -0
- package/dist/server/assets/SyncNowButton-BOn_IBXS.js +76 -0
- package/dist/server/assets/TimelineRouteFrame-CU7ilM7U.js +85 -0
- package/dist/server/assets/TweetRichText-DhCVv1-6.js +74 -0
- package/dist/server/assets/_tanstack-start-manifest_v-C-ZRnG_n.js +148 -0
- package/dist/server/assets/account-selection-CTdFGWRk.js +44 -0
- package/dist/server/assets/blocks-C1mgGTB3.js +299 -0
- package/dist/server/assets/blocks-DSgQghqP.js +17 -0
- package/dist/server/assets/bookmarks-BJN8tykY.js +14 -0
- package/dist/server/assets/client-stream-contracts-C2xbWVjm.js +769 -0
- package/dist/server/assets/data-sources-BnA1Q94p.js +180 -0
- package/dist/server/assets/discuss-CJ1dau1j.js +15 -0
- package/dist/server/assets/discuss-QTo08NCN.js +338 -0
- package/dist/server/assets/dms-BuLj4bp8.js +368 -0
- package/dist/server/assets/dms-DAyQrVM2.js +240 -0
- package/dist/server/assets/empty-plugin-adapters-DzrEkdBF.js +5 -0
- package/dist/server/assets/inbox-CkGOaW1q.js +214 -0
- package/dist/server/assets/inbox-DpWd1EzC.js +146 -0
- package/dist/server/assets/likes-DBOAtsJU.js +14 -0
- package/dist/server/assets/links-DAhSjynu.js +240 -0
- package/dist/server/assets/links-DmH5lxHO.js +468 -0
- package/dist/server/assets/mentions-BH2MzKtB.js +26 -0
- package/dist/server/assets/network-map-D3CzRcC4.js +224 -0
- package/dist/server/assets/network-map-D5DoLOwR.js +582 -0
- package/dist/server/assets/profile-analyze-BP65PED0.js +90 -0
- package/dist/server/assets/profile-analyze-C1NoM0tV.js +9 -0
- package/dist/server/assets/profile-hydration-client-GJ4kwJS8.js +133 -0
- package/dist/server/assets/profiles._handle-DjwlN_ac.js +184 -0
- package/dist/server/assets/profiles._handle-DsLNj2Ca.js +12 -0
- package/dist/server/assets/query-client-BdauHBgd.js +923 -0
- package/dist/server/assets/rate-limits-VAr2qzYD.js +262 -0
- package/dist/server/assets/route-search-BUlQ5lGM.js +117 -0
- package/dist/server/assets/router-DqkwSPiM.js +17026 -0
- package/dist/server/assets/routes-A9c9ZhwN.js +26 -0
- package/dist/server/assets/start-FaDOTmto.js +4 -0
- package/dist/server/assets/today-B5hVM57Z.js +17 -0
- package/dist/server/assets/today-fvGiZ7ud.js +303 -0
- package/dist/server/assets/ui-qbmcMSSX.js +137 -0
- package/{src/components/useDebouncedValue.ts → dist/server/assets/useDebouncedValue-DOnA6Xcy.js} +4 -4
- package/dist/server/assets/useTimelineRouteData-27cxynxa.js +1101 -0
- package/dist/server/server.js +5520 -0
- package/package.json +26 -34
- package/playwright.config.ts +0 -35
- package/scripts/browser-perf.mjs +0 -427
- package/scripts/build-docs-site.mjs +0 -967
- package/scripts/docs-site-assets.mjs +0 -311
- package/scripts/run-vitest.mjs +0 -21
- package/scripts/sanitize-node-options.mjs +0 -23
- package/scripts/start-test-server.mjs +0 -42
- package/src/cli/command-context.ts +0 -17
- package/src/cli/register-analysis.ts +0 -500
- package/src/cli/register-compose.ts +0 -40
- package/src/cli/register-graph.ts +0 -132
- package/src/cli/register-inbox.ts +0 -41
- package/src/cli/register-storage.ts +0 -106
- package/src/cli-moderation.ts +0 -210
- package/src/cli.ts +0 -1616
- package/src/components/AccountSwitcher.tsx +0 -178
- package/src/components/AppNav.tsx +0 -118
- package/src/components/AvatarChip.tsx +0 -58
- package/src/components/AvatarPreload.ts +0 -149
- package/src/components/BrandMark.tsx +0 -67
- package/src/components/ConversationThread.tsx +0 -131
- package/src/components/DmWorkspace.tsx +0 -286
- package/src/components/EmbeddedTweetCard.tsx +0 -55
- package/src/components/FeedState.tsx +0 -147
- package/src/components/FloatingPreview.tsx +0 -382
- package/src/components/InboxCard.tsx +0 -152
- package/src/components/LinkPreviewCard.tsx +0 -292
- package/src/components/MarkdownCitations.tsx +0 -680
- package/src/components/MarkdownViewer.tsx +0 -111
- package/src/components/ProfileAnalysisClient.ts +0 -191
- package/src/components/ProfileAnalysisStream.tsx +0 -259
- package/src/components/ProfilePreview.tsx +0 -131
- package/src/components/SavedTimelineView.tsx +0 -155
- package/src/components/SmartTimestamp.tsx +0 -72
- package/src/components/SyncNowButton.tsx +0 -140
- package/src/components/ThemeSlider.tsx +0 -80
- package/src/components/TimelineCard.tsx +0 -533
- package/src/components/TimelineRouteFrame.tsx +0 -186
- package/src/components/TweetArticleCard.tsx +0 -66
- package/src/components/TweetMediaGrid.tsx +0 -185
- package/src/components/TweetRichText.tsx +0 -159
- package/src/components/account-selection.ts +0 -64
- package/src/components/links-controller.ts +0 -162
- package/src/components/links-model.ts +0 -198
- package/src/components/network-map-controller.ts +0 -84
- package/src/components/network-map-model.ts +0 -255
- package/src/components/useTimelineRouteData.ts +0 -193
- package/src/lib/account-sync-job.ts +0 -666
- package/src/lib/actions-transport.ts +0 -300
- package/src/lib/analysis-runtime.ts +0 -238
- package/src/lib/api-client.ts +0 -220
- package/src/lib/api-contracts.ts +0 -328
- package/src/lib/archive-finder.ts +0 -185
- package/src/lib/archive-import-plan.ts +0 -102
- package/src/lib/archive-import.ts +0 -2695
- package/src/lib/authored-live.ts +0 -1027
- package/src/lib/avatar-cache.ts +0 -349
- package/src/lib/backup.ts +0 -2652
- package/src/lib/bird-actions.ts +0 -294
- package/src/lib/bird-command.ts +0 -130
- package/src/lib/bird.ts +0 -1140
- package/src/lib/blocklist.ts +0 -136
- package/src/lib/blocks-write.ts +0 -171
- package/src/lib/blocks.ts +0 -387
- package/src/lib/bookmark-sync-job.ts +0 -536
- package/src/lib/config.ts +0 -224
- package/src/lib/conversation-surface.ts +0 -205
- package/src/lib/data-sources.ts +0 -219
- package/src/lib/database-metrics.ts +0 -88
- package/src/lib/database-migrations.ts +0 -34
- package/src/lib/database-schema.ts +0 -312
- package/src/lib/database-writer.ts +0 -69
- package/src/lib/db.ts +0 -889
- package/src/lib/dm-read-model.ts +0 -533
- package/src/lib/dms-live.ts +0 -870
- package/src/lib/effect-runtime.ts +0 -45
- package/src/lib/follow-graph.ts +0 -1087
- package/src/lib/geocoding.ts +0 -296
- package/src/lib/http-effect.ts +0 -222
- package/src/lib/identity-search-index.ts +0 -369
- package/src/lib/import-repository.ts +0 -138
- package/src/lib/inbox.ts +0 -242
- package/src/lib/link-index.ts +0 -641
- package/src/lib/link-insights.ts +0 -860
- package/src/lib/link-preview-metadata.ts +0 -754
- package/src/lib/live-sync-engine.ts +0 -209
- package/src/lib/live-transport-gateway.ts +0 -128
- package/src/lib/location.ts +0 -137
- package/src/lib/media-fetch.ts +0 -860
- package/src/lib/media-includes.ts +0 -165
- package/src/lib/mention-threads-live.ts +0 -830
- package/src/lib/mentions-export.ts +0 -147
- package/src/lib/mentions-live.ts +0 -1114
- package/src/lib/moderation-target.ts +0 -227
- package/src/lib/moderation-write.ts +0 -131
- package/src/lib/mutes-write.ts +0 -171
- package/src/lib/mutes.ts +0 -84
- package/src/lib/ndjson-stream.ts +0 -106
- package/src/lib/network-map.ts +0 -382
- package/src/lib/openai-response-runtime.ts +0 -251
- package/src/lib/openai.ts +0 -117
- package/src/lib/paginated-sync.ts +0 -93
- package/src/lib/period-digest.ts +0 -1364
- package/src/lib/present.ts +0 -118
- package/src/lib/profile-affiliation-hydration.ts +0 -228
- package/src/lib/profile-affiliations.ts +0 -262
- package/src/lib/profile-analysis.ts +0 -1198
- package/src/lib/profile-bio-entities.ts +0 -318
- package/src/lib/profile-history.ts +0 -164
- package/src/lib/profile-hydration.ts +0 -180
- package/src/lib/profile-replies.ts +0 -77
- package/src/lib/profile-resolver.ts +0 -650
- package/src/lib/queries.ts +0 -6
- package/src/lib/query-actions.ts +0 -437
- package/src/lib/query-client.tsx +0 -47
- package/src/lib/query-read-model-shared.ts +0 -52
- package/src/lib/query-read-models.ts +0 -5
- package/src/lib/query-resource.ts +0 -41
- package/src/lib/query-status.ts +0 -164
- package/src/lib/research.ts +0 -652
- package/src/lib/runtime-services.ts +0 -20
- package/src/lib/search-discussion.ts +0 -812
- package/src/lib/seed.ts +0 -613
- package/src/lib/server-runtime-services.ts +0 -30
- package/src/lib/sqlite.ts +0 -183
- package/src/lib/streaming-ingestion.ts +0 -240
- package/src/lib/sync-cache.ts +0 -70
- package/src/lib/sync-plan.ts +0 -175
- package/src/lib/theme-transition.ts +0 -113
- package/src/lib/theme.tsx +0 -165
- package/src/lib/timeline-collections-live.ts +0 -407
- package/src/lib/timeline-live.ts +0 -302
- package/src/lib/timeline-read-model.ts +0 -1191
- package/src/lib/tweet-account-edges.ts +0 -48
- package/src/lib/tweet-lookup.ts +0 -46
- package/src/lib/tweet-render.ts +0 -430
- package/src/lib/tweet-repository.ts +0 -156
- package/src/lib/tweet-search-live.ts +0 -462
- package/src/lib/types.ts +0 -723
- package/src/lib/ui.ts +0 -529
- package/src/lib/url-expansion-store.ts +0 -110
- package/src/lib/url-expansion.ts +0 -343
- package/src/lib/url-safety.ts +0 -220
- package/src/lib/web-sync.ts +0 -533
- package/src/lib/whois.ts +0 -1080
- package/src/lib/x-profile.ts +0 -365
- package/src/lib/x-web.ts +0 -199
- package/src/lib/xurl-rate-limits.ts +0 -267
- package/src/lib/xurl.ts +0 -1677
- package/src/routeTree.gen.ts +0 -723
- package/src/router.tsx +0 -20
- package/src/routes/__root.tsx +0 -82
- package/src/routes/api/action.tsx +0 -147
- package/src/routes/api/avatar.tsx +0 -50
- package/src/routes/api/blocks.tsx +0 -35
- package/src/routes/api/conversation.tsx +0 -45
- package/src/routes/api/data-sources.tsx +0 -24
- package/src/routes/api/inbox.tsx +0 -43
- package/src/routes/api/link-insights.tsx +0 -85
- package/src/routes/api/link-preview.tsx +0 -47
- package/src/routes/api/network-map.tsx +0 -55
- package/src/routes/api/period-digest.tsx +0 -101
- package/src/routes/api/profile-analysis.tsx +0 -97
- package/src/routes/api/profile-hydrate.tsx +0 -54
- package/src/routes/api/query.tsx +0 -121
- package/src/routes/api/search-discussion.tsx +0 -113
- package/src/routes/api/status.tsx +0 -30
- package/src/routes/api/sync.tsx +0 -108
- package/src/routes/api/xurl-rate-limits.tsx +0 -24
- package/src/routes/blocks.tsx +0 -343
- package/src/routes/bookmarks.tsx +0 -18
- package/src/routes/data-sources.tsx +0 -249
- package/src/routes/discuss.tsx +0 -419
- package/src/routes/dms.tsx +0 -415
- package/src/routes/inbox.tsx +0 -232
- package/src/routes/index.tsx +0 -32
- package/src/routes/likes.tsx +0 -18
- package/src/routes/links.tsx +0 -631
- package/src/routes/mentions.tsx +0 -32
- package/src/routes/network-map.tsx +0 -732
- package/src/routes/profile-analyze.tsx +0 -112
- package/src/routes/profiles.$handle.tsx +0 -228
- package/src/routes/rate-limits.tsx +0 -305
- package/src/routes/today.tsx +0 -511
- package/src/styles.css +0 -257
- package/tsconfig.json +0 -29
- package/vite.config.ts +0 -29
- /package/{public → dist/client}/birdclaw-mark.png +0 -0
- /package/{public → dist/client}/favicon.ico +0 -0
- /package/{public → dist/client}/logo192.png +0 -0
- /package/{public → dist/client}/logo512.png +0 -0
- /package/{public → dist/client}/manifest.json +0 -0
- /package/{public → dist/client}/robots.txt +0 -0
package/src/lib/link-index.ts
DELETED
|
@@ -1,641 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { getNativeDb } from "./db";
|
|
3
|
-
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
4
|
-
import type { Database } from "./sqlite";
|
|
5
|
-
import {
|
|
6
|
-
normalizeUrlExpansionForIndex,
|
|
7
|
-
upsertUrlExpansion,
|
|
8
|
-
} from "./url-expansion-store";
|
|
9
|
-
import {
|
|
10
|
-
expandUrls,
|
|
11
|
-
extractUrls,
|
|
12
|
-
type ExpandUrlsOptions,
|
|
13
|
-
} from "./url-expansion";
|
|
14
|
-
import type {
|
|
15
|
-
LinkIndexItem,
|
|
16
|
-
LinkOccurrenceItem,
|
|
17
|
-
LinkSearchItem,
|
|
18
|
-
ProfileRecord,
|
|
19
|
-
TimelineItem,
|
|
20
|
-
TweetEntities,
|
|
21
|
-
TweetMediaItem,
|
|
22
|
-
} from "./types";
|
|
23
|
-
|
|
24
|
-
const DEFAULT_EXPAND_CONCURRENCY = 12;
|
|
25
|
-
|
|
26
|
-
interface TweetUrlEntityLike {
|
|
27
|
-
url?: unknown;
|
|
28
|
-
expandedUrl?: unknown;
|
|
29
|
-
expanded_url?: unknown;
|
|
30
|
-
displayUrl?: unknown;
|
|
31
|
-
display_url?: unknown;
|
|
32
|
-
title?: unknown;
|
|
33
|
-
description?: unknown;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface SourceUrl {
|
|
37
|
-
url: string;
|
|
38
|
-
expandedUrl?: string;
|
|
39
|
-
title?: string;
|
|
40
|
-
description?: string | null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface LinkBackfillOptions {
|
|
44
|
-
includeAllUrls?: boolean;
|
|
45
|
-
refresh?: boolean;
|
|
46
|
-
source?: "dm" | "tweet";
|
|
47
|
-
limit?: number;
|
|
48
|
-
concurrency?: number;
|
|
49
|
-
fetchImpl?: ExpandUrlsOptions["fetchImpl"];
|
|
50
|
-
timeoutMs?: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface LinkBackfillResult {
|
|
54
|
-
occurrences: number;
|
|
55
|
-
uniqueUrls: number;
|
|
56
|
-
entityExpansions: number;
|
|
57
|
-
networkExpansions: number;
|
|
58
|
-
cacheExpansions: number;
|
|
59
|
-
misses: number;
|
|
60
|
-
errors: number;
|
|
61
|
-
remainingUnexpanded: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface LinkSearchOptions {
|
|
65
|
-
since?: string;
|
|
66
|
-
until?: string;
|
|
67
|
-
account?: string;
|
|
68
|
-
source?: "dm" | "tweet";
|
|
69
|
-
direction?: "inbound" | "outbound";
|
|
70
|
-
participant?: string;
|
|
71
|
-
mediaType?: "image" | "video" | "gif";
|
|
72
|
-
limit?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function parseJsonField<T>(value: unknown, fallback: T): T {
|
|
76
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
77
|
-
return fallback;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
return JSON.parse(value) as T;
|
|
82
|
-
} catch {
|
|
83
|
-
return fallback;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function getString(value: unknown) {
|
|
88
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function isIndexedUrl(url: string, includeAllUrls: boolean) {
|
|
92
|
-
if (includeAllUrls) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const host = new URL(url).hostname.toLowerCase();
|
|
98
|
-
return host === "t.co" || host.endsWith(".t.co");
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function toTweetEntityUrls(entitiesJson: string): SourceUrl[] {
|
|
105
|
-
const entities = parseJsonField<TweetEntities>(entitiesJson, {});
|
|
106
|
-
const urls = Array.isArray(entities.urls) ? entities.urls : [];
|
|
107
|
-
return urls
|
|
108
|
-
.map((entry: TweetUrlEntityLike) => {
|
|
109
|
-
const url = getString(entry.url);
|
|
110
|
-
const expandedUrl =
|
|
111
|
-
getString(entry.expandedUrl) ?? getString(entry.expanded_url);
|
|
112
|
-
if (!url) {
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
url,
|
|
117
|
-
...(expandedUrl ? { expandedUrl } : {}),
|
|
118
|
-
...(getString(entry.title) ? { title: getString(entry.title) } : {}),
|
|
119
|
-
...(entry.description !== undefined
|
|
120
|
-
? { description: getString(entry.description) ?? null }
|
|
121
|
-
: {}),
|
|
122
|
-
};
|
|
123
|
-
})
|
|
124
|
-
.filter((entry): entry is SourceUrl => Boolean(entry));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function uniqueSourceUrls(
|
|
128
|
-
text: string,
|
|
129
|
-
entityUrls: SourceUrl[],
|
|
130
|
-
includeAllUrls: boolean,
|
|
131
|
-
) {
|
|
132
|
-
const byUrl = new Map<string, SourceUrl>();
|
|
133
|
-
for (const url of extractUrls(text)) {
|
|
134
|
-
if (isIndexedUrl(url, includeAllUrls)) {
|
|
135
|
-
byUrl.set(url, { url });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
for (const entityUrl of entityUrls) {
|
|
139
|
-
if (!isIndexedUrl(entityUrl.url, includeAllUrls)) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
byUrl.set(entityUrl.url, { ...byUrl.get(entityUrl.url), ...entityUrl });
|
|
143
|
-
}
|
|
144
|
-
return Array.from(byUrl.values());
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function rebuildOccurrences(
|
|
148
|
-
db: Database,
|
|
149
|
-
includeAllUrls: boolean,
|
|
150
|
-
source: LinkBackfillOptions["source"],
|
|
151
|
-
) {
|
|
152
|
-
const insert = db.prepare(`
|
|
153
|
-
insert or replace into link_occurrences (
|
|
154
|
-
source_kind, source_id, source_position, short_url, account_id,
|
|
155
|
-
conversation_id, direction, created_at
|
|
156
|
-
) values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
157
|
-
`);
|
|
158
|
-
let occurrences = 0;
|
|
159
|
-
let entityExpansions = 0;
|
|
160
|
-
const now = new Date().toISOString();
|
|
161
|
-
|
|
162
|
-
db.transaction(() => {
|
|
163
|
-
if (source) {
|
|
164
|
-
db.prepare("delete from link_occurrences where source_kind = ?").run(
|
|
165
|
-
source,
|
|
166
|
-
);
|
|
167
|
-
} else {
|
|
168
|
-
db.exec("delete from link_occurrences");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const dmRows =
|
|
172
|
-
source === "tweet"
|
|
173
|
-
? []
|
|
174
|
-
: (db
|
|
175
|
-
.prepare(`
|
|
176
|
-
select m.id, m.conversation_id, c.account_id, m.direction, m.created_at, m.text
|
|
177
|
-
from dm_messages m
|
|
178
|
-
join dm_conversations c on c.id = m.conversation_id
|
|
179
|
-
where m.text like '%://%'
|
|
180
|
-
`)
|
|
181
|
-
.all() as Array<Record<string, unknown>>);
|
|
182
|
-
for (const row of dmRows) {
|
|
183
|
-
const urls = uniqueSourceUrls(String(row.text), [], includeAllUrls);
|
|
184
|
-
urls.forEach((entry, index) => {
|
|
185
|
-
insert.run(
|
|
186
|
-
"dm",
|
|
187
|
-
String(row.id),
|
|
188
|
-
index,
|
|
189
|
-
entry.url,
|
|
190
|
-
String(row.account_id),
|
|
191
|
-
String(row.conversation_id),
|
|
192
|
-
String(row.direction),
|
|
193
|
-
String(row.created_at),
|
|
194
|
-
);
|
|
195
|
-
occurrences++;
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const tweetRows =
|
|
200
|
-
source === "dm"
|
|
201
|
-
? []
|
|
202
|
-
: (db
|
|
203
|
-
.prepare(`
|
|
204
|
-
select id, account_id, created_at, text, entities_json
|
|
205
|
-
from tweets
|
|
206
|
-
where text like '%://%' or entities_json like '%://%'
|
|
207
|
-
`)
|
|
208
|
-
.all() as Array<Record<string, unknown>>);
|
|
209
|
-
for (const row of tweetRows) {
|
|
210
|
-
const entityUrls = toTweetEntityUrls(String(row.entities_json));
|
|
211
|
-
const urls = uniqueSourceUrls(
|
|
212
|
-
String(row.text),
|
|
213
|
-
entityUrls,
|
|
214
|
-
includeAllUrls,
|
|
215
|
-
);
|
|
216
|
-
urls.forEach((entry, index) => {
|
|
217
|
-
insert.run(
|
|
218
|
-
"tweet",
|
|
219
|
-
String(row.id),
|
|
220
|
-
index,
|
|
221
|
-
entry.url,
|
|
222
|
-
String(row.account_id),
|
|
223
|
-
null,
|
|
224
|
-
null,
|
|
225
|
-
String(row.created_at),
|
|
226
|
-
);
|
|
227
|
-
occurrences++;
|
|
228
|
-
if (entry.expandedUrl) {
|
|
229
|
-
upsertUrlExpansion(
|
|
230
|
-
db,
|
|
231
|
-
normalizeUrlExpansionForIndex({
|
|
232
|
-
url: entry.url,
|
|
233
|
-
expandedUrl: entry.expandedUrl,
|
|
234
|
-
finalUrl: entry.expandedUrl,
|
|
235
|
-
status: "hit",
|
|
236
|
-
title: entry.title,
|
|
237
|
-
description: entry.description,
|
|
238
|
-
source: "entity",
|
|
239
|
-
updatedAt: now,
|
|
240
|
-
}),
|
|
241
|
-
);
|
|
242
|
-
entityExpansions++;
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
})();
|
|
247
|
-
|
|
248
|
-
return { occurrences, entityExpansions };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function expandWithConcurrencyEffect(
|
|
252
|
-
db: Database,
|
|
253
|
-
urls: string[],
|
|
254
|
-
options: LinkBackfillOptions,
|
|
255
|
-
): Effect.Effect<
|
|
256
|
-
{
|
|
257
|
-
networkExpansions: number;
|
|
258
|
-
cacheExpansions: number;
|
|
259
|
-
misses: number;
|
|
260
|
-
errors: number;
|
|
261
|
-
},
|
|
262
|
-
unknown
|
|
263
|
-
> {
|
|
264
|
-
const concurrency = Math.max(
|
|
265
|
-
1,
|
|
266
|
-
Math.min(options.concurrency ?? DEFAULT_EXPAND_CONCURRENCY, 64),
|
|
267
|
-
);
|
|
268
|
-
const counts = {
|
|
269
|
-
networkExpansions: 0,
|
|
270
|
-
cacheExpansions: 0,
|
|
271
|
-
misses: 0,
|
|
272
|
-
errors: 0,
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
return Effect.forEach(
|
|
276
|
-
urls,
|
|
277
|
-
(url) =>
|
|
278
|
-
tryPromise(() =>
|
|
279
|
-
expandUrls([url], {
|
|
280
|
-
refresh: options.refresh,
|
|
281
|
-
fetchImpl: options.fetchImpl,
|
|
282
|
-
timeoutMs: options.timeoutMs,
|
|
283
|
-
}),
|
|
284
|
-
).pipe(
|
|
285
|
-
Effect.map((results) => {
|
|
286
|
-
const result = results[0]!;
|
|
287
|
-
if (result.source === "network") {
|
|
288
|
-
counts.networkExpansions++;
|
|
289
|
-
} else {
|
|
290
|
-
counts.cacheExpansions++;
|
|
291
|
-
}
|
|
292
|
-
if (result.status === "miss") {
|
|
293
|
-
counts.misses++;
|
|
294
|
-
}
|
|
295
|
-
if (result.status === "error") {
|
|
296
|
-
counts.errors++;
|
|
297
|
-
}
|
|
298
|
-
upsertUrlExpansion(db, normalizeUrlExpansionForIndex(result));
|
|
299
|
-
}),
|
|
300
|
-
),
|
|
301
|
-
{ concurrency },
|
|
302
|
-
).pipe(Effect.as(counts));
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function backfillLinkIndexEffect(
|
|
306
|
-
options: LinkBackfillOptions = {},
|
|
307
|
-
): Effect.Effect<LinkBackfillResult, unknown> {
|
|
308
|
-
return Effect.gen(function* () {
|
|
309
|
-
const db = getNativeDb({ seedDemoData: false });
|
|
310
|
-
const { occurrences, entityExpansions } = rebuildOccurrences(
|
|
311
|
-
db,
|
|
312
|
-
Boolean(options.includeAllUrls),
|
|
313
|
-
options.source,
|
|
314
|
-
);
|
|
315
|
-
const limit =
|
|
316
|
-
typeof options.limit === "number" && Number.isFinite(options.limit)
|
|
317
|
-
? Math.max(0, Math.trunc(options.limit))
|
|
318
|
-
: undefined;
|
|
319
|
-
const needsExpansionClause = options.refresh
|
|
320
|
-
? "1 = 1"
|
|
321
|
-
: "(e.short_url is null or e.status in ('error', 'miss'))";
|
|
322
|
-
|
|
323
|
-
const urlsToExpand = db
|
|
324
|
-
.prepare(`
|
|
325
|
-
select distinct o.short_url
|
|
326
|
-
from link_occurrences o
|
|
327
|
-
left join url_expansions e on e.short_url = o.short_url
|
|
328
|
-
where ${needsExpansionClause}
|
|
329
|
-
${options.source ? "and o.source_kind = ?" : ""}
|
|
330
|
-
order by o.short_url
|
|
331
|
-
${limit === undefined ? "" : "limit ?"}
|
|
332
|
-
`)
|
|
333
|
-
.all(
|
|
334
|
-
...(options.source ? [options.source] : []),
|
|
335
|
-
...(limit === undefined ? [] : [limit]),
|
|
336
|
-
) as Array<{
|
|
337
|
-
short_url: string;
|
|
338
|
-
}>;
|
|
339
|
-
|
|
340
|
-
const expansionCounts = yield* expandWithConcurrencyEffect(
|
|
341
|
-
db,
|
|
342
|
-
urlsToExpand.map((row) => row.short_url),
|
|
343
|
-
options,
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
const uniqueUrls = db
|
|
347
|
-
.prepare(`
|
|
348
|
-
select count(distinct short_url) as count
|
|
349
|
-
from link_occurrences
|
|
350
|
-
${options.source ? "where source_kind = ?" : ""}
|
|
351
|
-
`)
|
|
352
|
-
.get(...(options.source ? [options.source] : [])) as { count: number };
|
|
353
|
-
const remaining = db
|
|
354
|
-
.prepare(`
|
|
355
|
-
select count(distinct o.short_url) as count
|
|
356
|
-
from link_occurrences o
|
|
357
|
-
left join url_expansions e on e.short_url = o.short_url
|
|
358
|
-
where (e.short_url is null or e.status in ('error', 'miss'))
|
|
359
|
-
${options.source ? "and o.source_kind = ?" : ""}
|
|
360
|
-
`)
|
|
361
|
-
.get(...(options.source ? [options.source] : [])) as { count: number };
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
occurrences,
|
|
365
|
-
uniqueUrls: Number(uniqueUrls.count),
|
|
366
|
-
entityExpansions,
|
|
367
|
-
...expansionCounts,
|
|
368
|
-
remainingUnexpanded: Number(remaining.count),
|
|
369
|
-
};
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export function backfillLinkIndex(
|
|
374
|
-
options: LinkBackfillOptions = {},
|
|
375
|
-
): Promise<LinkBackfillResult> {
|
|
376
|
-
return runEffectPromise(backfillLinkIndexEffect(options));
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function likePattern(value: string) {
|
|
380
|
-
return `%${value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_").toLowerCase()}%`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function toProfile(
|
|
384
|
-
row: Record<string, unknown>,
|
|
385
|
-
prefix: string,
|
|
386
|
-
): ProfileRecord | null {
|
|
387
|
-
if (!row[`${prefix}id`]) {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
return {
|
|
391
|
-
id: String(row[`${prefix}id`]),
|
|
392
|
-
handle: String(row[`${prefix}handle`]),
|
|
393
|
-
displayName: String(row[`${prefix}display_name`]),
|
|
394
|
-
bio: String(row[`${prefix}bio`]),
|
|
395
|
-
followersCount: Number(row[`${prefix}followers_count`]),
|
|
396
|
-
followingCount: Number(row[`${prefix}following_count`]),
|
|
397
|
-
avatarHue: Number(row[`${prefix}avatar_hue`]),
|
|
398
|
-
avatarUrl: getString(row[`${prefix}avatar_url`]),
|
|
399
|
-
location: getString(row[`${prefix}location`]),
|
|
400
|
-
url: getString(row[`${prefix}url`]),
|
|
401
|
-
verifiedType: getString(row[`${prefix}verified_type`]),
|
|
402
|
-
entities: parseJsonField<Record<string, unknown>>(
|
|
403
|
-
row[`${prefix}entities_json`],
|
|
404
|
-
{},
|
|
405
|
-
),
|
|
406
|
-
createdAt: String(row[`${prefix}created_at`]),
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function toLinkedTweet(row: Record<string, unknown>): TimelineItem | null {
|
|
411
|
-
const author = toProfile(row, "linked_author_");
|
|
412
|
-
if (!row.linked_id || !author) {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
id: String(row.linked_id),
|
|
418
|
-
accountId: String(row.linked_account_id),
|
|
419
|
-
accountHandle: String(row.linked_account_handle ?? ""),
|
|
420
|
-
kind: String(row.linked_kind) as TimelineItem["kind"],
|
|
421
|
-
text: String(row.linked_text),
|
|
422
|
-
createdAt: String(row.linked_created_at),
|
|
423
|
-
isReplied: Boolean(row.linked_is_replied),
|
|
424
|
-
likeCount: Number(row.linked_like_count),
|
|
425
|
-
mediaCount: Number(row.linked_media_count),
|
|
426
|
-
bookmarked: Boolean(row.linked_bookmarked),
|
|
427
|
-
liked: Boolean(row.linked_liked),
|
|
428
|
-
author,
|
|
429
|
-
entities: parseJsonField<TweetEntities>(row.linked_entities_json, {}),
|
|
430
|
-
media: parseJsonField<TweetMediaItem[]>(row.linked_media_json, []),
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function toLinkSearchItem(row: Record<string, unknown>): LinkSearchItem {
|
|
435
|
-
const occurrence: LinkOccurrenceItem = {
|
|
436
|
-
sourceKind: String(row.source_kind) as LinkOccurrenceItem["sourceKind"],
|
|
437
|
-
sourceId: String(row.source_id),
|
|
438
|
-
sourcePosition: Number(row.source_position),
|
|
439
|
-
shortUrl: String(row.short_url),
|
|
440
|
-
accountId: getString(row.account_id) ?? null,
|
|
441
|
-
conversationId: getString(row.conversation_id) ?? null,
|
|
442
|
-
direction: getString(row.direction) ?? null,
|
|
443
|
-
createdAt: String(row.created_at),
|
|
444
|
-
};
|
|
445
|
-
return {
|
|
446
|
-
occurrence,
|
|
447
|
-
expansion: {
|
|
448
|
-
shortUrl: String(row.short_url),
|
|
449
|
-
expandedUrl: String(row.expanded_url),
|
|
450
|
-
finalUrl: String(row.final_url),
|
|
451
|
-
status: String(row.status) as LinkIndexItem["status"],
|
|
452
|
-
expandedTweetId: getString(row.expanded_tweet_id) ?? null,
|
|
453
|
-
expandedHandle: getString(row.expanded_handle) ?? null,
|
|
454
|
-
title: getString(row.title) ?? null,
|
|
455
|
-
description: getString(row.description) ?? null,
|
|
456
|
-
imageUrl: getString(row.image_url) ?? null,
|
|
457
|
-
siteName: getString(row.site_name) ?? null,
|
|
458
|
-
error: getString(row.error) ?? null,
|
|
459
|
-
source: String(row.expansion_source),
|
|
460
|
-
updatedAt: String(row.updated_at),
|
|
461
|
-
},
|
|
462
|
-
sourceText: String(row.source_text),
|
|
463
|
-
sourceAuthor: toProfile(row, "source_author_"),
|
|
464
|
-
participant: toProfile(row, "participant_"),
|
|
465
|
-
linkedTweet: toLinkedTweet(row),
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export function searchLinks(query: string, options: LinkSearchOptions = {}) {
|
|
470
|
-
const db = getNativeDb({ seedDemoData: false });
|
|
471
|
-
const conditions: string[] = [];
|
|
472
|
-
const params: unknown[] = [];
|
|
473
|
-
const searchFields = `
|
|
474
|
-
lower(
|
|
475
|
-
coalesce(e.short_url, '') || ' ' ||
|
|
476
|
-
coalesce(e.expanded_url, '') || ' ' ||
|
|
477
|
-
coalesce(e.final_url, '') || ' ' ||
|
|
478
|
-
coalesce(e.expanded_handle, '') || ' ' ||
|
|
479
|
-
coalesce(e.title, '') || ' ' ||
|
|
480
|
-
coalesce(e.description, '') || ' ' ||
|
|
481
|
-
coalesce(dm.text, '') || ' ' ||
|
|
482
|
-
coalesce(source_tweet.text, '') || ' ' ||
|
|
483
|
-
coalesce(linked.text, '') || ' ' ||
|
|
484
|
-
coalesce(linked_author.handle, '') || ' ' ||
|
|
485
|
-
coalesce(linked_author.display_name, '')
|
|
486
|
-
)
|
|
487
|
-
`;
|
|
488
|
-
|
|
489
|
-
for (const term of query.match(/[\p{L}\p{N}_:.@/-]+/gu) ?? []) {
|
|
490
|
-
conditions.push(`${searchFields} like ? escape '\\'`);
|
|
491
|
-
params.push(likePattern(term));
|
|
492
|
-
}
|
|
493
|
-
if (options.since) {
|
|
494
|
-
conditions.push("o.created_at >= ?");
|
|
495
|
-
params.push(options.since);
|
|
496
|
-
}
|
|
497
|
-
if (options.until) {
|
|
498
|
-
conditions.push("o.created_at < ?");
|
|
499
|
-
params.push(options.until);
|
|
500
|
-
}
|
|
501
|
-
if (options.account) {
|
|
502
|
-
conditions.push("(o.account_id = ? or account.handle = ?)");
|
|
503
|
-
params.push(options.account, options.account.replace(/^@/, ""));
|
|
504
|
-
}
|
|
505
|
-
if (options.source) {
|
|
506
|
-
conditions.push("o.source_kind = ?");
|
|
507
|
-
params.push(options.source);
|
|
508
|
-
}
|
|
509
|
-
if (options.direction) {
|
|
510
|
-
conditions.push("o.direction = ?");
|
|
511
|
-
params.push(options.direction);
|
|
512
|
-
}
|
|
513
|
-
if (options.participant) {
|
|
514
|
-
conditions.push(
|
|
515
|
-
"(participant.handle = ? or participant.display_name like ?)",
|
|
516
|
-
);
|
|
517
|
-
params.push(
|
|
518
|
-
options.participant.replace(/^@/, ""),
|
|
519
|
-
`%${options.participant}%`,
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
if (options.mediaType) {
|
|
523
|
-
conditions.push(
|
|
524
|
-
"(linked.media_json like ? or source_tweet.media_json like ?)",
|
|
525
|
-
);
|
|
526
|
-
params.push(
|
|
527
|
-
`%"type":"${options.mediaType}"%`,
|
|
528
|
-
`%"type":"${options.mediaType}"%`,
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const limit =
|
|
533
|
-
options.limit && Number.isFinite(options.limit)
|
|
534
|
-
? Math.max(1, Math.trunc(options.limit))
|
|
535
|
-
: 20;
|
|
536
|
-
params.push(limit);
|
|
537
|
-
|
|
538
|
-
const rows = db
|
|
539
|
-
.prepare(`
|
|
540
|
-
select
|
|
541
|
-
o.source_kind,
|
|
542
|
-
o.source_id,
|
|
543
|
-
o.source_position,
|
|
544
|
-
o.short_url,
|
|
545
|
-
o.account_id,
|
|
546
|
-
o.conversation_id,
|
|
547
|
-
o.direction,
|
|
548
|
-
o.created_at,
|
|
549
|
-
e.expanded_url,
|
|
550
|
-
e.final_url,
|
|
551
|
-
e.status,
|
|
552
|
-
e.expanded_tweet_id,
|
|
553
|
-
e.expanded_handle,
|
|
554
|
-
e.title,
|
|
555
|
-
e.description,
|
|
556
|
-
e.image_url,
|
|
557
|
-
e.site_name,
|
|
558
|
-
e.error,
|
|
559
|
-
e.source as expansion_source,
|
|
560
|
-
e.updated_at,
|
|
561
|
-
coalesce(dm.text, source_tweet.text, '') as source_text,
|
|
562
|
-
account.handle as account_handle,
|
|
563
|
-
source_author.id as source_author_id,
|
|
564
|
-
source_author.handle as source_author_handle,
|
|
565
|
-
source_author.display_name as source_author_display_name,
|
|
566
|
-
source_author.bio as source_author_bio,
|
|
567
|
-
source_author.followers_count as source_author_followers_count,
|
|
568
|
-
source_author.following_count as source_author_following_count,
|
|
569
|
-
source_author.avatar_hue as source_author_avatar_hue,
|
|
570
|
-
source_author.avatar_url as source_author_avatar_url,
|
|
571
|
-
source_author.location as source_author_location,
|
|
572
|
-
source_author.url as source_author_url,
|
|
573
|
-
source_author.verified_type as source_author_verified_type,
|
|
574
|
-
source_author.entities_json as source_author_entities_json,
|
|
575
|
-
source_author.created_at as source_author_created_at,
|
|
576
|
-
participant.id as participant_id,
|
|
577
|
-
participant.handle as participant_handle,
|
|
578
|
-
participant.display_name as participant_display_name,
|
|
579
|
-
participant.bio as participant_bio,
|
|
580
|
-
participant.followers_count as participant_followers_count,
|
|
581
|
-
participant.following_count as participant_following_count,
|
|
582
|
-
participant.avatar_hue as participant_avatar_hue,
|
|
583
|
-
participant.avatar_url as participant_avatar_url,
|
|
584
|
-
participant.location as participant_location,
|
|
585
|
-
participant.url as participant_url,
|
|
586
|
-
participant.verified_type as participant_verified_type,
|
|
587
|
-
participant.entities_json as participant_entities_json,
|
|
588
|
-
participant.created_at as participant_created_at,
|
|
589
|
-
linked.id as linked_id,
|
|
590
|
-
linked.account_id as linked_account_id,
|
|
591
|
-
linked_account.handle as linked_account_handle,
|
|
592
|
-
linked.kind as linked_kind,
|
|
593
|
-
linked.text as linked_text,
|
|
594
|
-
linked.created_at as linked_created_at,
|
|
595
|
-
linked.is_replied as linked_is_replied,
|
|
596
|
-
linked.like_count as linked_like_count,
|
|
597
|
-
linked.media_count as linked_media_count,
|
|
598
|
-
linked.bookmarked as linked_bookmarked,
|
|
599
|
-
linked.liked as linked_liked,
|
|
600
|
-
linked.entities_json as linked_entities_json,
|
|
601
|
-
linked.media_json as linked_media_json,
|
|
602
|
-
linked_author.id as linked_author_id,
|
|
603
|
-
linked_author.handle as linked_author_handle,
|
|
604
|
-
linked_author.display_name as linked_author_display_name,
|
|
605
|
-
linked_author.bio as linked_author_bio,
|
|
606
|
-
linked_author.followers_count as linked_author_followers_count,
|
|
607
|
-
linked_author.following_count as linked_author_following_count,
|
|
608
|
-
linked_author.avatar_hue as linked_author_avatar_hue,
|
|
609
|
-
linked_author.avatar_url as linked_author_avatar_url,
|
|
610
|
-
linked_author.location as linked_author_location,
|
|
611
|
-
linked_author.url as linked_author_url,
|
|
612
|
-
linked_author.verified_type as linked_author_verified_type,
|
|
613
|
-
linked_author.entities_json as linked_author_entities_json,
|
|
614
|
-
linked_author.created_at as linked_author_created_at
|
|
615
|
-
from link_occurrences o
|
|
616
|
-
join url_expansions e on e.short_url = o.short_url
|
|
617
|
-
left join accounts account on account.id = o.account_id
|
|
618
|
-
left join dm_messages dm
|
|
619
|
-
on o.source_kind = 'dm' and dm.id = o.source_id
|
|
620
|
-
left join dm_conversations conversation
|
|
621
|
-
on conversation.id = o.conversation_id
|
|
622
|
-
left join profiles participant
|
|
623
|
-
on participant.id = conversation.participant_profile_id
|
|
624
|
-
left join tweets source_tweet
|
|
625
|
-
on o.source_kind = 'tweet' and source_tweet.id = o.source_id
|
|
626
|
-
left join profiles source_author
|
|
627
|
-
on source_author.id = source_tweet.author_profile_id
|
|
628
|
-
left join tweets linked
|
|
629
|
-
on linked.id = e.expanded_tweet_id
|
|
630
|
-
left join accounts linked_account
|
|
631
|
-
on linked_account.id = linked.account_id
|
|
632
|
-
left join profiles linked_author
|
|
633
|
-
on linked_author.id = linked.author_profile_id
|
|
634
|
-
${conditions.length > 0 ? `where ${conditions.join(" and ")}` : ""}
|
|
635
|
-
order by o.created_at desc
|
|
636
|
-
limit ?
|
|
637
|
-
`)
|
|
638
|
-
.all(...params) as Array<Record<string, unknown>>;
|
|
639
|
-
|
|
640
|
-
return rows.map(toLinkSearchItem);
|
|
641
|
-
}
|