Page MenuHomePhabricator

D13775.id45485.diff
No OneTemporary

D13775.id45485.diff

diff --git a/keyserver/src/responders/farcaster-webhook-responders.js b/keyserver/src/responders/farcaster-webhook-responders.js
--- a/keyserver/src/responders/farcaster-webhook-responders.js
+++ b/keyserver/src/responders/farcaster-webhook-responders.js
@@ -3,16 +3,108 @@
import { createHmac } from 'crypto';
import type { $Request } from 'express';
+import bots from 'lib/facts/bots.js';
+import { extractKeyserverIDFromID } from 'lib/keyserver-conn/keyserver-call-utils.js';
import { type NeynarWebhookCastCreatedEvent } from 'lib/types/farcaster-types.js';
+import { threadTypes } from 'lib/types/thread-types-enum.js';
+import { type NewThreadResponse } from 'lib/types/thread-types.js';
import { neynarWebhookCastCreatedEventValidator } from 'lib/types/validators/farcaster-webhook-validators.js';
import { ServerError } from 'lib/utils/errors.js';
import { assertWithValidator } from 'lib/utils/validation-utils.js';
+import {
+ getFarcasterChannelTagBlob,
+ createOrUpdateFarcasterChannelTag,
+ farcasterChannelTagBlobValidator,
+} from '../creators/farcaster-channel-tag-creator.js';
+import { createThread } from '../creators/thread-creator.js';
+import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js';
+import { createBotViewer } from '../session/bots.js';
+import { updateRole } from '../updaters/thread-updaters.js';
+import { thisKeyserverAdmin, thisKeyserverID } from '../user/identity.js';
+import { getFarcasterBotConfig } from '../utils/farcaster-bot.js';
+import { getVerifiedUserIDForFID } from '../utils/farcaster-utils.js';
+import { neynarClient } from '../utils/fc-cache.js';
import { getNeynarConfig } from '../utils/neynar-utils.js';
const taggedCommFarcasterInputValidator =
neynarWebhookCastCreatedEventValidator;
+const noChannelCommunityID = '80887273';
+const { commbot } = bots;
+const commbotViewer = createBotViewer(commbot.userID);
+
+async function createTaggedFarcasterCommunity(
+ channelID: string,
+ taggerUserID: ?string,
+): Promise<NewThreadResponse> {
+ const neynarChannel =
+ await neynarClient?.fetchFarcasterChannelByID(channelID);
+ if (!neynarChannel) {
+ throw new ServerError('channel_not_found');
+ }
+
+ const leadFID = neynarChannel.lead.fid.toString();
+ const leadUserID = await getVerifiedUserIDForFID(leadFID);
+ const keyserverAdmin = await thisKeyserverAdmin();
+ const communityAdminID = leadUserID
+ ? leadUserID
+ : taggerUserID || keyserverAdmin.id;
+
+ const farcasterUserIDs: $ReadOnlyArray<string> = [
+ leadUserID,
+ taggerUserID,
+ ].filter(Boolean);
+
+ const initialMemberIDs = farcasterUserIDs.includes(communityAdminID)
+ ? farcasterUserIDs
+ : [...farcasterUserIDs, communityAdminID];
+
+ const newThreadResponse = await createThread(
+ commbotViewer,
+ {
+ type: threadTypes.COMMUNITY_ROOT,
+ name: neynarChannel.name,
+ initialMemberIDs,
+ },
+ {
+ forceAddMembers: true,
+ addViewerAsGhost: true,
+ },
+ );
+
+ const { newThreadID } = newThreadResponse;
+ const fetchThreadResult = await fetchServerThreadInfos({
+ threadID: newThreadResponse.newThreadID,
+ });
+ const threadInfo = fetchThreadResult.threadInfos[newThreadID];
+
+ if (!threadInfo) {
+ throw new ServerError('fetch_failed');
+ }
+
+ const adminRoleID = Object.keys(threadInfo.roles).find(
+ roleID => threadInfo.roles[roleID].name === 'Admins',
+ );
+
+ if (!adminRoleID) {
+ throw new ServerError('community_missing_admin');
+ }
+
+ await updateRole(commbotViewer, {
+ threadID: newThreadID,
+ memberIDs: [communityAdminID],
+ role: adminRoleID,
+ });
+
+ await createOrUpdateFarcasterChannelTag(commbotViewer, {
+ commCommunityID: newThreadResponse.newThreadID,
+ farcasterChannelID: channelID,
+ });
+
+ return newThreadResponse;
+}
+
async function verifyNeynarWebhookSignature(
signature: string,
event: NeynarWebhookCastCreatedEvent,
@@ -38,10 +130,62 @@
throw new ServerError('missing_neynar_signature');
}
- const isValidSignature = await verifyNeynarWebhookSignature(signature, event);
+ const [isValidSignature, farcasterBotConfig] = await Promise.all([
+ verifyNeynarWebhookSignature(signature, event),
+ getFarcasterBotConfig(),
+ ]);
+
if (!isValidSignature) {
throw new ServerError('invalid_webhook_signature');
}
+
+ const eventChannelID = event.data.channel?.id;
+ const eventTaggerFID = event.data.author.fid;
+
+ const taggerUserID = await getVerifiedUserIDForFID(eventTaggerFID.toString());
+
+ const isAuthoritativeFarcasterBot =
+ farcasterBotConfig.authoritativeFarcasterBot ?? false;
+
+ if (!eventChannelID && !isAuthoritativeFarcasterBot) {
+ return;
+ }
+
+ let channelCommunityID = noChannelCommunityID;
+ if (eventChannelID) {
+ const blobDownload = await getFarcasterChannelTagBlob(eventChannelID);
+ if (blobDownload.found) {
+ const blobText = await blobDownload.blob.text();
+ const blobObject = JSON.parse(blobText);
+ const farcasterChannelTagBlob = assertWithValidator(
+ blobObject,
+ farcasterChannelTagBlobValidator,
+ );
+
+ const { commCommunityID } = farcasterChannelTagBlob;
+ const commCommunityKeyserverID =
+ extractKeyserverIDFromID(commCommunityID);
+ const keyserverID = await thisKeyserverID();
+
+ if (keyserverID !== commCommunityKeyserverID) {
+ return;
+ }
+ channelCommunityID = commCommunityID.split('|')[1];
+ } else if (!blobDownload.found && blobDownload.status === 404) {
+ if (!isAuthoritativeFarcasterBot) {
+ return;
+ }
+
+ const newThreadResponse = await createTaggedFarcasterCommunity(
+ eventChannelID,
+ taggerUserID,
+ );
+ channelCommunityID = newThreadResponse.newThreadID;
+ } else {
+ throw new ServerError('blob_fetch_failed');
+ }
+ }
+ console.log(channelCommunityID);
}
export { taggedCommFarcasterResponder, taggedCommFarcasterInputValidator };

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 6:58 PM (10 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2580728
Default Alt Text
D13775.id45485.diff (5 KB)

Event Timeline