birdclaw 0.5.1 → 0.7.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/CHANGELOG.md +92 -1
- package/README.md +75 -5
- package/package.json +8 -2
- package/scripts/browser-perf.mjs +1 -0
- package/scripts/start-test-server.mjs +16 -3
- package/src/cli.ts +812 -37
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +37 -7
- package/src/components/AvatarChip.tsx +9 -3
- package/src/components/DmWorkspace.tsx +18 -8
- package/src/components/LinkPreviewCard.tsx +40 -18
- package/src/components/MarkdownViewer.tsx +818 -0
- package/src/components/ProfileAnalysisStream.tsx +428 -0
- package/src/components/ProfilePreview.tsx +120 -9
- package/src/components/SavedTimelineView.tsx +30 -8
- package/src/components/SyncNowButton.tsx +60 -25
- package/src/components/ThemeSlider.tsx +55 -50
- package/src/components/TimelineCard.tsx +240 -92
- package/src/components/TimelineRouteFrame.tsx +38 -8
- package/src/components/TweetMediaGrid.tsx +87 -38
- package/src/components/TweetRichText.tsx +45 -17
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +97 -13
- package/src/lib/account-sync-job.ts +666 -0
- package/src/lib/actions-transport.ts +216 -146
- package/src/lib/api-client.ts +128 -53
- package/src/lib/archive-finder.ts +78 -63
- package/src/lib/archive-import.ts +1593 -1291
- package/src/lib/authored-live.ts +262 -204
- package/src/lib/avatar-cache.ts +208 -43
- package/src/lib/backup.ts +1536 -954
- package/src/lib/bird-actions.ts +139 -57
- package/src/lib/bird-command.ts +101 -28
- package/src/lib/bird.ts +582 -194
- package/src/lib/blocklist.ts +40 -23
- package/src/lib/blocks-write.ts +129 -80
- package/src/lib/blocks.ts +165 -97
- package/src/lib/bookmark-sync-job.ts +250 -160
- package/src/lib/config.ts +35 -2
- package/src/lib/conversation-surface.ts +79 -48
- package/src/lib/data-sources.ts +219 -0
- package/src/lib/db.ts +95 -4
- package/src/lib/dms-live.ts +720 -66
- package/src/lib/effect-runtime.ts +45 -0
- package/src/lib/follow-graph.ts +224 -180
- package/src/lib/geocoding.ts +296 -0
- package/src/lib/http-effect.ts +222 -0
- package/src/lib/inbox.ts +74 -43
- package/src/lib/link-index.ts +88 -76
- package/src/lib/link-insights.ts +24 -0
- package/src/lib/link-preview-metadata.ts +472 -52
- package/src/lib/location.ts +137 -0
- package/src/lib/media-fetch.ts +286 -213
- package/src/lib/mention-threads-live.ts +445 -288
- package/src/lib/mentions-live.ts +549 -354
- package/src/lib/moderation-target.ts +102 -65
- package/src/lib/moderation-write.ts +77 -18
- package/src/lib/mutes-write.ts +129 -80
- package/src/lib/mutes.ts +8 -1
- package/src/lib/network-map.ts +382 -0
- package/src/lib/openai.ts +84 -53
- package/src/lib/period-digest.ts +1317 -0
- package/src/lib/profile-affiliation-hydration.ts +93 -54
- package/src/lib/profile-analysis.ts +1272 -0
- package/src/lib/profile-bio-entities.ts +1 -1
- package/src/lib/profile-hydration.ts +124 -72
- package/src/lib/profile-replies.ts +60 -43
- package/src/lib/profile-resolver.ts +402 -294
- package/src/lib/queries.ts +983 -203
- package/src/lib/research.ts +165 -120
- package/src/lib/search-discussion.ts +1016 -0
- package/src/lib/sqlite.ts +1 -0
- package/src/lib/timeline-collections-live.ts +204 -167
- package/src/lib/timeline-live.ts +325 -51
- package/src/lib/tweet-account-edges.ts +2 -0
- package/src/lib/tweet-lookup.ts +30 -19
- package/src/lib/tweet-render.ts +141 -1
- package/src/lib/tweet-search-live.ts +565 -0
- package/src/lib/types.ts +75 -3
- package/src/lib/ui.ts +31 -8
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +222 -149
- package/src/lib/whois.ts +166 -147
- package/src/lib/x-web.ts +102 -71
- package/src/lib/xurl-rate-limits.ts +267 -0
- package/src/lib/xurl.ts +1185 -405
- package/src/routeTree.gen.ts +273 -0
- package/src/routes/__root.tsx +24 -5
- package/src/routes/api/action.tsx +127 -78
- package/src/routes/api/avatar.tsx +39 -30
- package/src/routes/api/blocks.tsx +26 -23
- package/src/routes/api/conversation.tsx +25 -14
- package/src/routes/api/data-sources.tsx +24 -0
- package/src/routes/api/inbox.tsx +27 -21
- package/src/routes/api/link-insights.tsx +31 -25
- package/src/routes/api/link-preview.tsx +25 -21
- package/src/routes/api/network-map.tsx +55 -0
- package/src/routes/api/period-digest.tsx +133 -0
- package/src/routes/api/profile-analysis.tsx +152 -0
- package/src/routes/api/profile-hydrate.tsx +31 -28
- package/src/routes/api/query.tsx +80 -55
- package/src/routes/api/search-discussion.tsx +169 -0
- package/src/routes/api/status.tsx +18 -10
- package/src/routes/api/sync.tsx +75 -29
- package/src/routes/api/xurl-rate-limits.tsx +24 -0
- package/src/routes/data-sources.tsx +255 -0
- package/src/routes/discuss.tsx +419 -0
- package/src/routes/dms.tsx +95 -28
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/network-map.tsx +1035 -0
- package/src/routes/profile-analyze.tsx +112 -0
- package/src/routes/profiles.$handle.tsx +228 -0
- package/src/routes/rate-limits.tsx +309 -0
- package/src/routes/today.tsx +455 -0
- package/src/styles.css +22 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
|
+
import { Effect } from "effect";
|
|
2
3
|
import { lookupProfileViaBird } from "./bird-actions";
|
|
3
4
|
import { getNativeDb } from "./db";
|
|
5
|
+
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
4
6
|
import type { ProfileRecord, XurlMentionUser } from "./types";
|
|
5
7
|
import { getExternalUserId, upsertProfileFromXUser } from "./x-profile";
|
|
6
8
|
import {
|
|
@@ -14,6 +16,17 @@ export interface ResolvedModerationProfile {
|
|
|
14
16
|
externalUserId: string | null;
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
function toError(error: unknown) {
|
|
20
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function trySync<T>(try_: () => T) {
|
|
24
|
+
return Effect.try({
|
|
25
|
+
try: try_,
|
|
26
|
+
catch: toError,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
export function toProfile(row: Record<string, unknown>): ProfileRecord {
|
|
18
31
|
const followingCount = Number(row.following_count ?? 0);
|
|
19
32
|
return {
|
|
@@ -90,90 +103,114 @@ export function resolveLocalProfile(
|
|
|
90
103
|
};
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
export
|
|
106
|
+
export function resolveProfileEffect(
|
|
94
107
|
query: string,
|
|
95
|
-
):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
): Effect.Effect<ResolvedModerationProfile, unknown> {
|
|
109
|
+
return Effect.gen(function* () {
|
|
110
|
+
const db = yield* trySync(() => getNativeDb());
|
|
111
|
+
const normalizedQuery = normalizeProfileQuery(query);
|
|
112
|
+
if (!normalizedQuery) {
|
|
113
|
+
return yield* Effect.fail(new Error("Missing profile handle or id"));
|
|
114
|
+
}
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
const local = yield* trySync(() =>
|
|
117
|
+
resolveLocalProfile(db, normalizedQuery),
|
|
118
|
+
);
|
|
119
|
+
if (process.env.BIRDCLAW_DISABLE_LIVE_PROFILE_LOOKUP === "1") {
|
|
120
|
+
if (local) {
|
|
121
|
+
return local;
|
|
122
|
+
}
|
|
123
|
+
return yield* Effect.fail(
|
|
124
|
+
new Error(`Profile not found locally: ${query}`),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (
|
|
128
|
+
local &&
|
|
129
|
+
!local.profile.id.startsWith("profile_group_") &&
|
|
130
|
+
local.externalUserId
|
|
131
|
+
) {
|
|
105
132
|
return local;
|
|
106
133
|
}
|
|
107
|
-
throw new Error(`Profile not found locally: ${query}`);
|
|
108
|
-
}
|
|
109
|
-
if (
|
|
110
|
-
local &&
|
|
111
|
-
!local.profile.id.startsWith("profile_group_") &&
|
|
112
|
-
local.externalUserId
|
|
113
|
-
) {
|
|
114
|
-
return local;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let user: XurlMentionUser | undefined;
|
|
118
|
-
let lastError: unknown;
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
let user: XurlMentionUser | undefined;
|
|
136
|
+
let lastError: unknown;
|
|
137
|
+
|
|
138
|
+
const birdResult = yield* tryPromise(() =>
|
|
139
|
+
lookupProfileViaBird(local?.profile.handle ?? normalizedQuery),
|
|
140
|
+
).pipe(
|
|
141
|
+
Effect.map((value) => ({ ok: true as const, value })),
|
|
142
|
+
Effect.catchAll((error) => Effect.succeed({ ok: false as const, error })),
|
|
143
|
+
);
|
|
144
|
+
if (birdResult.ok) {
|
|
145
|
+
user = birdResult.value ?? undefined;
|
|
146
|
+
} else {
|
|
147
|
+
lastError = birdResult.error;
|
|
148
|
+
}
|
|
127
149
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
if (!user) {
|
|
151
|
+
const xurlResult = yield* tryPromise(() =>
|
|
152
|
+
/^\d+$/.test(normalizedQuery)
|
|
153
|
+
? lookupUsersByIds([normalizedQuery])
|
|
154
|
+
: lookupUsersByHandles([local?.profile.handle ?? normalizedQuery]),
|
|
155
|
+
).pipe(
|
|
156
|
+
Effect.map((value) => ({ ok: true as const, value })),
|
|
157
|
+
Effect.catchAll((error) =>
|
|
158
|
+
Effect.succeed({ ok: false as const, error }),
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
if (xurlResult.ok) {
|
|
162
|
+
[user] = xurlResult.value;
|
|
132
163
|
} else {
|
|
133
|
-
|
|
134
|
-
local?.profile.handle ?? normalizedQuery,
|
|
135
|
-
]);
|
|
164
|
+
lastError = xurlResult.error;
|
|
136
165
|
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
lastError = error;
|
|
139
166
|
}
|
|
140
|
-
}
|
|
141
167
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
168
|
+
if (!user && lastError) {
|
|
169
|
+
if (local) {
|
|
170
|
+
return local;
|
|
171
|
+
}
|
|
172
|
+
return yield* Effect.fail(lastError);
|
|
145
173
|
}
|
|
146
|
-
throw lastError;
|
|
147
|
-
}
|
|
148
174
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
175
|
+
if (!user) {
|
|
176
|
+
if (local) {
|
|
177
|
+
return local;
|
|
178
|
+
}
|
|
179
|
+
return yield* Effect.fail(new Error(`Profile not found: ${query}`));
|
|
152
180
|
}
|
|
153
|
-
throw new Error(`Profile not found: ${query}`);
|
|
154
|
-
}
|
|
155
181
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
182
|
+
if (local) {
|
|
183
|
+
return yield* trySync(() => upsertProfileFromXUser(db, user));
|
|
184
|
+
}
|
|
159
185
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
186
|
+
const username = String(user.username ?? "").replace(/^@/, "");
|
|
187
|
+
if (username) {
|
|
188
|
+
const localByHandle = yield* trySync(() =>
|
|
189
|
+
resolveLocalProfile(db, username),
|
|
190
|
+
);
|
|
191
|
+
if (localByHandle) {
|
|
192
|
+
return yield* trySync(() => upsertProfileFromXUser(db, user));
|
|
193
|
+
}
|
|
165
194
|
}
|
|
166
|
-
}
|
|
167
195
|
|
|
168
|
-
|
|
196
|
+
return yield* trySync(() => upsertProfileFromXUser(db, user));
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function resolveProfile(
|
|
201
|
+
query: string,
|
|
202
|
+
): Promise<ResolvedModerationProfile> {
|
|
203
|
+
return runEffectPromise(resolveProfileEffect(query));
|
|
169
204
|
}
|
|
170
205
|
|
|
171
|
-
export
|
|
172
|
-
|
|
173
|
-
const me =
|
|
206
|
+
export function getAuthenticatedUserIdEffect() {
|
|
207
|
+
return Effect.gen(function* () {
|
|
208
|
+
const me = yield* tryPromise(() => lookupAuthenticatedUser());
|
|
174
209
|
const id = me?.id;
|
|
175
210
|
return typeof id === "string" && id.length > 0 ? id : null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
211
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getAuthenticatedUserId() {
|
|
215
|
+
return runEffectPromise(getAuthenticatedUserIdEffect());
|
|
179
216
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
|
+
import { Effect } from "effect";
|
|
2
3
|
import type { ActionsTransport } from "./config";
|
|
3
4
|
import { getNativeDb } from "./db";
|
|
5
|
+
import { runEffectPromise } from "./effect-runtime";
|
|
4
6
|
import {
|
|
5
7
|
getAccountHandle,
|
|
6
8
|
getDefaultAccountId,
|
|
7
9
|
normalizeProfileQuery,
|
|
8
|
-
|
|
10
|
+
resolveProfileEffect,
|
|
9
11
|
} from "./moderation-target";
|
|
12
|
+
import { getExternalUserId } from "./x-profile";
|
|
10
13
|
|
|
11
14
|
export interface ModerationActionOptions {
|
|
12
15
|
transport?: ActionsTransport;
|
|
@@ -18,28 +21,84 @@ interface ResolveModerationTargetParams {
|
|
|
18
21
|
selfActionError: string;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
function toError(error: unknown) {
|
|
25
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trySync<T>(try_: () => T) {
|
|
29
|
+
return Effect.try({
|
|
30
|
+
try: try_,
|
|
31
|
+
catch: toError,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveModerationTargetEffect({
|
|
22
36
|
accountId,
|
|
23
37
|
query,
|
|
24
38
|
selfActionError,
|
|
25
39
|
}: ResolveModerationTargetParams) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
return Effect.gen(function* () {
|
|
41
|
+
const db = yield* trySync(() => getNativeDb());
|
|
42
|
+
const resolvedAccountId = accountId || getDefaultAccountId(db);
|
|
43
|
+
const accountHandle = yield* trySync(() =>
|
|
44
|
+
getAccountHandle(db, resolvedAccountId),
|
|
45
|
+
);
|
|
46
|
+
if (!accountHandle) {
|
|
47
|
+
return yield* Effect.fail(
|
|
48
|
+
new Error(`Unknown account: ${resolvedAccountId}`),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const normalizedQuery = normalizeProfileQuery(query);
|
|
52
|
+
if (normalizedQuery.toLowerCase() === accountHandle.toLowerCase()) {
|
|
53
|
+
return yield* Effect.fail(new Error(selfActionError));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resolved = yield* resolveProfileEffect(query);
|
|
57
|
+
const account = yield* trySync(
|
|
58
|
+
() =>
|
|
59
|
+
db
|
|
60
|
+
.prepare("select handle, external_user_id from accounts where id = ?")
|
|
61
|
+
.get(resolvedAccountId) as
|
|
62
|
+
| { handle: string; external_user_id: string | null }
|
|
63
|
+
| undefined,
|
|
64
|
+
);
|
|
65
|
+
const accountProfile = yield* trySync(() =>
|
|
66
|
+
account
|
|
67
|
+
? (db
|
|
68
|
+
.prepare("select id from profiles where handle = ? limit 1")
|
|
69
|
+
.get(account.handle.replace(/^@/, "")) as
|
|
70
|
+
| { id: string }
|
|
71
|
+
| undefined)
|
|
72
|
+
: undefined,
|
|
73
|
+
);
|
|
74
|
+
const accountExternalUserId =
|
|
75
|
+
account?.external_user_id ?? getExternalUserId(accountProfile?.id ?? "");
|
|
76
|
+
if (
|
|
77
|
+
resolved.profile.handle.toLowerCase() === accountHandle.toLowerCase() ||
|
|
78
|
+
(accountProfile?.id && resolved.profile.id === accountProfile.id) ||
|
|
79
|
+
(accountExternalUserId &&
|
|
80
|
+
resolved.externalUserId === accountExternalUserId)
|
|
81
|
+
) {
|
|
82
|
+
return yield* Effect.fail(new Error(selfActionError));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
db,
|
|
87
|
+
resolved,
|
|
88
|
+
resolvedAccountId,
|
|
89
|
+
accountIdentity: {
|
|
90
|
+
id: resolvedAccountId,
|
|
91
|
+
handle: account?.handle ?? accountHandle,
|
|
92
|
+
externalUserId: accountExternalUserId || null,
|
|
93
|
+
},
|
|
94
|
+
actionQuery:
|
|
95
|
+
resolved.externalUserId ?? resolved.profile.handle ?? normalizedQuery,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
32
99
|
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
db,
|
|
36
|
-
resolved,
|
|
37
|
-
resolvedAccountId,
|
|
38
|
-
actionQuery:
|
|
39
|
-
resolved.externalUserId ??
|
|
40
|
-
resolved.profile.handle ??
|
|
41
|
-
normalizeProfileQuery(query),
|
|
42
|
-
};
|
|
100
|
+
export function resolveModerationTarget(params: ResolveModerationTargetParams) {
|
|
101
|
+
return runEffectPromise(resolveModerationTargetEffect(params));
|
|
43
102
|
}
|
|
44
103
|
|
|
45
104
|
export function writeModerationRow(
|
package/src/lib/mutes-write.ts
CHANGED
|
@@ -1,118 +1,167 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
1
2
|
import { runModerationAction } from "./actions-transport";
|
|
3
|
+
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
2
4
|
import {
|
|
3
5
|
deleteModerationRow,
|
|
4
6
|
type ModerationActionOptions,
|
|
5
|
-
|
|
7
|
+
resolveModerationTargetEffect,
|
|
6
8
|
writeModerationRow,
|
|
7
9
|
} from "./moderation-write";
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
function trySync<T>(try_: () => T) {
|
|
12
|
+
return Effect.try({
|
|
13
|
+
try: try_,
|
|
14
|
+
catch: (error) =>
|
|
15
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function addMuteEffect(
|
|
10
20
|
accountId: string,
|
|
11
21
|
query: string,
|
|
12
22
|
options: ModerationActionOptions = {},
|
|
13
23
|
) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
return Effect.gen(function* () {
|
|
25
|
+
const { db, resolved, resolvedAccountId, accountIdentity, actionQuery } =
|
|
26
|
+
yield* resolveModerationTargetEffect({
|
|
27
|
+
accountId,
|
|
28
|
+
query,
|
|
29
|
+
selfActionError: "Cannot mute the current account",
|
|
30
|
+
});
|
|
31
|
+
const transport = yield* tryPromise(() =>
|
|
32
|
+
runModerationAction({
|
|
33
|
+
action: "mute",
|
|
34
|
+
query: actionQuery,
|
|
35
|
+
targetUserId: resolved.externalUserId ?? undefined,
|
|
36
|
+
transport: options.transport,
|
|
37
|
+
expectedAccount: accountIdentity,
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!transport.ok) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
action: "mute",
|
|
45
|
+
accountId: resolvedAccountId,
|
|
46
|
+
profile: resolved.profile,
|
|
47
|
+
transport,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const mutedAt = new Date().toISOString();
|
|
52
|
+
yield* trySync(() =>
|
|
53
|
+
writeModerationRow(
|
|
54
|
+
db,
|
|
55
|
+
"mutes",
|
|
56
|
+
resolvedAccountId,
|
|
57
|
+
resolved.profile.id,
|
|
58
|
+
mutedAt,
|
|
59
|
+
),
|
|
60
|
+
);
|
|
26
61
|
|
|
27
|
-
if (!transport.ok) {
|
|
28
62
|
return {
|
|
29
|
-
ok:
|
|
63
|
+
ok: true,
|
|
30
64
|
action: "mute",
|
|
31
65
|
accountId: resolvedAccountId,
|
|
66
|
+
mutedAt,
|
|
32
67
|
profile: resolved.profile,
|
|
33
68
|
transport,
|
|
34
69
|
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const mutedAt = new Date().toISOString();
|
|
38
|
-
writeModerationRow(
|
|
39
|
-
db,
|
|
40
|
-
"mutes",
|
|
41
|
-
resolvedAccountId,
|
|
42
|
-
resolved.profile.id,
|
|
43
|
-
mutedAt,
|
|
44
|
-
);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
45
72
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
transport,
|
|
53
|
-
};
|
|
73
|
+
export function addMute(
|
|
74
|
+
accountId: string,
|
|
75
|
+
query: string,
|
|
76
|
+
options: ModerationActionOptions = {},
|
|
77
|
+
) {
|
|
78
|
+
return runEffectPromise(addMuteEffect(accountId, query, options));
|
|
54
79
|
}
|
|
55
80
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
export function recordMuteEffect(accountId: string, query: string) {
|
|
82
|
+
return Effect.gen(function* () {
|
|
83
|
+
const { db, resolved, resolvedAccountId } =
|
|
84
|
+
yield* resolveModerationTargetEffect({
|
|
85
|
+
accountId,
|
|
86
|
+
query,
|
|
87
|
+
selfActionError: "Cannot mute the current account",
|
|
88
|
+
});
|
|
62
89
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
const mutedAt = new Date().toISOString();
|
|
91
|
+
yield* trySync(() =>
|
|
92
|
+
writeModerationRow(
|
|
93
|
+
db,
|
|
94
|
+
"mutes",
|
|
95
|
+
resolvedAccountId,
|
|
96
|
+
resolved.profile.id,
|
|
97
|
+
mutedAt,
|
|
98
|
+
),
|
|
99
|
+
);
|
|
71
100
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
action: "record-mute",
|
|
104
|
+
accountId: resolvedAccountId,
|
|
105
|
+
mutedAt,
|
|
106
|
+
profile: resolved.profile,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function recordMute(accountId: string, query: string) {
|
|
112
|
+
return runEffectPromise(recordMuteEffect(accountId, query));
|
|
79
113
|
}
|
|
80
114
|
|
|
81
|
-
export
|
|
115
|
+
export function removeMuteEffect(
|
|
82
116
|
accountId: string,
|
|
83
117
|
query: string,
|
|
84
118
|
options: ModerationActionOptions = {},
|
|
85
119
|
) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
return Effect.gen(function* () {
|
|
121
|
+
const { db, resolved, resolvedAccountId, accountIdentity, actionQuery } =
|
|
122
|
+
yield* resolveModerationTargetEffect({
|
|
123
|
+
accountId,
|
|
124
|
+
query,
|
|
125
|
+
selfActionError: "Cannot mute the current account",
|
|
126
|
+
});
|
|
127
|
+
const transport = yield* tryPromise(() =>
|
|
128
|
+
runModerationAction({
|
|
129
|
+
action: "unmute",
|
|
130
|
+
query: actionQuery,
|
|
131
|
+
targetUserId: resolved.externalUserId ?? undefined,
|
|
132
|
+
transport: options.transport,
|
|
133
|
+
expectedAccount: accountIdentity,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!transport.ok) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
action: "unmute",
|
|
141
|
+
accountId: resolvedAccountId,
|
|
142
|
+
profile: resolved.profile,
|
|
143
|
+
transport,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
yield* trySync(() =>
|
|
148
|
+
deleteModerationRow(db, "mutes", resolvedAccountId, resolved.profile.id),
|
|
149
|
+
);
|
|
98
150
|
|
|
99
|
-
if (!transport.ok) {
|
|
100
151
|
return {
|
|
101
|
-
ok:
|
|
152
|
+
ok: true,
|
|
102
153
|
action: "unmute",
|
|
103
154
|
accountId: resolvedAccountId,
|
|
104
155
|
profile: resolved.profile,
|
|
105
156
|
transport,
|
|
106
157
|
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
deleteModerationRow(db, "mutes", resolvedAccountId, resolved.profile.id);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
110
160
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
161
|
+
export function removeMute(
|
|
162
|
+
accountId: string,
|
|
163
|
+
query: string,
|
|
164
|
+
options: ModerationActionOptions = {},
|
|
165
|
+
) {
|
|
166
|
+
return runEffectPromise(removeMuteEffect(accountId, query, options));
|
|
118
167
|
}
|
package/src/lib/mutes.ts
CHANGED
|
@@ -2,7 +2,14 @@ import { getNativeDb } from "./db";
|
|
|
2
2
|
import { toProfile } from "./moderation-target";
|
|
3
3
|
import type { BlockItem } from "./types";
|
|
4
4
|
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
addMute,
|
|
7
|
+
addMuteEffect,
|
|
8
|
+
recordMute,
|
|
9
|
+
recordMuteEffect,
|
|
10
|
+
removeMute,
|
|
11
|
+
removeMuteEffect,
|
|
12
|
+
} from "./mutes-write";
|
|
6
13
|
|
|
7
14
|
export interface MuteItem {
|
|
8
15
|
accountId: string;
|