Skip to content

Commit a21e836

Browse files
committed
feat: add twitter verified followers rule
1 parent e95b226 commit a21e836

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

app/rules/icebreaker.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,19 @@ type IcebreakerProfile = {
6666
guilds?: IcebreakerGuildMembership[];
6767
};
6868

69+
export type UserMetadata = {
70+
name: string;
71+
value: string | number;
72+
};
73+
74+
export type UserMetadataApiResponse =
75+
| {
76+
metadata?: UserMetadata[];
77+
}
78+
| undefined;
79+
6980
const API_URL = "https://app.icebreaker.xyz/api/v1";
81+
const TWITTER_API_URL = "https://hook.us1.make.com/h4yqha7yee9juaosenjrmebd956va0ew?twitter=";
7082

7183
async function request<T>(path: string, options?: RequestInit) {
7284
try {
@@ -79,6 +91,34 @@ async function request<T>(path: string, options?: RequestInit) {
7991
}
8092
}
8193

94+
async function getTwitterMetadata(username?: string) {
95+
if (!username) {
96+
return;
97+
}
98+
99+
const response = await request<UserMetadataApiResponse>(`${TWITTER_API_URL}${username}`);
100+
101+
return response?.metadata;
102+
}
103+
104+
function extractTwitterFollowerCount(metadata?: UserMetadata[]) {
105+
if (!metadata) {
106+
return;
107+
}
108+
109+
const followerCount = metadata?.find(({ name }) => name === "followers_count")?.value as number | undefined;
110+
return followerCount;
111+
}
112+
113+
function extractTwitterCreatedAt(metadata?: UserMetadata[]) {
114+
if (!metadata) {
115+
return;
116+
}
117+
118+
const createdAt = metadata?.find(({ name }) => name === "created_at")?.value;
119+
return createdAt ? new Date(createdAt) : undefined;
120+
}
121+
82122
type ProfileResponse = {
83123
profiles: IcebreakerProfile[];
84124
};
@@ -212,6 +252,46 @@ async function hasIcebreakerLinkedAccount({ user: member, rule }: CheckFunctionA
212252
};
213253
}
214254

255+
async function hasGreaterThanVerifiedTwitterFollowers({ user: member, rule }: CheckFunctionArgs) {
256+
const { threshold } = rule.args as { threshold: string };
257+
258+
if (!threshold || isNaN(+threshold)) {
259+
return {
260+
result: false,
261+
message: "Invalid threshold value",
262+
};
263+
}
264+
const thresholdNumber = +threshold;
265+
266+
const user = await getIcebreakerbyFid(member.fid);
267+
268+
if (!user) {
269+
return {
270+
result: false,
271+
message: `@${member.username} not found in Icebreaker`,
272+
};
273+
}
274+
// get twitter followers for user verified twitter account
275+
const verifiedTwitterAccount = user.channels?.find(
276+
(channel) => channel.type === "twitter" && channel.isVerified
277+
)?.value;
278+
279+
const metadata = verifiedTwitterAccount ? await getTwitterMetadata(verifiedTwitterAccount) : undefined;
280+
281+
const followerCount = metadata && extractTwitterFollowerCount(metadata);
282+
283+
const userHasGreaterThanThreshold = !!followerCount && followerCount > thresholdNumber;
284+
285+
return {
286+
result: userHasGreaterThanThreshold,
287+
message: followerCount
288+
? `@${member.username} has ${followerCount} followers on Twitter`
289+
: metadata
290+
? `Unable to retrieve follower count for @${member.username}`
291+
: `Unable to find a verified Twitter account for @${member.username}`,
292+
};
293+
}
294+
215295
async function hasPOAP({ user: member, rule }: CheckFunctionArgs) {
216296
const { eventId } = rule.args as { eventId: string };
217297

@@ -265,7 +345,8 @@ type RuleName =
265345
| "hasIcebreakerVerified"
266346
| "hasIcebreakerLinkedAccount"
267347
| "hasPOAP"
268-
| "hasGuildRole";
348+
| "hasGuildRole"
349+
| "hasGreaterThanVerifiedTwitterFollowers";
269350

270351
const author = "Icebreaker";
271352
const authorUrl = "https://icebreaker.xyz";
@@ -377,6 +458,29 @@ export const iceBreakerRulesDefinitions: Record<RuleName, RuleDefinition> = {
377458
},
378459
},
379460

461+
hasGreaterThanVerifiedTwitterFollowers: {
462+
name: "hasGreaterThanVerifiedTwitterFollowers",
463+
allowMultiple: true,
464+
author,
465+
authorUrl,
466+
authorIcon,
467+
category: "all",
468+
friendlyName: "Icebreaker: Has Greater Than X Verified Twitter Followers",
469+
checkType: "user",
470+
description: "Check if the user has more than a certain number of followers on their verified Twitter account",
471+
hidden: false,
472+
invertable: true,
473+
args: {
474+
threshold: {
475+
type: "string",
476+
friendlyName: "Follower Threshold",
477+
description: "The minimum number of followers required",
478+
placeholder: "Enter a number...",
479+
required: true,
480+
},
481+
},
482+
},
483+
380484
hasPOAP: {
381485
name: "hasPOAP",
382486
allowMultiple: true,
@@ -441,4 +545,5 @@ export const iceBreakerRulesFunction: Record<RuleName, CheckFunction> = {
441545
hasIcebreakerLinkedAccount: hasIcebreakerLinkedAccount,
442546
hasPOAP: hasPOAP,
443547
hasGuildRole: hasGuildRole,
548+
hasGreaterThanVerifiedTwitterFollowers: hasGreaterThanVerifiedTwitterFollowers,
444549
} as const;

app/rules/rules.type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const ruleNames = [
3333
"hasIcebreakerVerified",
3434
"hasIcebreakerCredential",
3535
"hasIcebreakerLinkedAccount",
36+
"hasGreaterThanVerifiedTwitterFollowers",
3637
"hasPOAP",
3738
"hasGuildRole",
3839
"membershipFeeRequired",

0 commit comments

Comments
 (0)