forked from elizaOS/eliza
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
60 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,9 @@ import { | |
parseBooleanFromText, | ||
IAgentRuntime, | ||
ActionTimelineType, | ||
elizaLogger, | ||
} from "@elizaos/core"; | ||
import { z, ZodError } from "zod"; | ||
import { z } from "zod"; | ||
|
||
export const DEFAULT_MAX_TWEET_LENGTH = 280; | ||
|
||
|
@@ -30,41 +31,15 @@ export const twitterEnvSchema = z.object({ | |
TWITTER_DRY_RUN: z.boolean(), | ||
TWITTER_USERNAME: z.string().min(1, "X/Twitter username is required"), | ||
TWITTER_PASSWORD: z.string().min(1, "X/Twitter password is required"), | ||
TWITTER_EMAIL: z.string().email("Valid X/Twitter email is required"), | ||
TWITTER_EMAIL: z.string() | ||
.min(1, "X/Twitter email is required") | ||
.email("Invalid email format - must be a valid email address"), | ||
MAX_TWEET_LENGTH: z.number().int().default(DEFAULT_MAX_TWEET_LENGTH), | ||
TWITTER_SEARCH_ENABLE: z.boolean().default(false), | ||
TWITTER_2FA_SECRET: z.string(), | ||
TWITTER_RETRY_LIMIT: z.number().int(), | ||
TWITTER_POLL_INTERVAL: z.number().int(), | ||
TWITTER_TARGET_USERS: z.array(twitterUsernameSchema).default([]), | ||
// I guess it's possible to do the transformation with zod | ||
// not sure it's preferable, maybe a readability issue | ||
// since more people will know js/ts than zod | ||
/* | ||
z | ||
.string() | ||
.transform((val) => val.trim()) | ||
.pipe( | ||
z.string() | ||
.transform((val) => | ||
val ? val.split(',').map((u) => u.trim()).filter(Boolean) : [] | ||
) | ||
.pipe( | ||
z.array( | ||
z.string() | ||
.min(1) | ||
.max(15) | ||
.regex( | ||
/^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]$|^[A-Za-z]$/, | ||
'Invalid Twitter username format' | ||
) | ||
) | ||
) | ||
.transform((users) => users.join(',')) | ||
) | ||
.optional() | ||
.default(''), | ||
*/ | ||
POST_INTERVAL_MIN: z.number().int(), | ||
POST_INTERVAL_MAX: z.number().int(), | ||
ENABLE_ACTION_PROCESSING: z.boolean(), | ||
|
@@ -79,20 +54,6 @@ export const twitterEnvSchema = z.object({ | |
|
||
export type TwitterConfig = z.infer<typeof twitterEnvSchema>; | ||
|
||
/** | ||
* Helper to parse a comma-separated list of Twitter usernames | ||
* (already present in your code). | ||
*/ | ||
function parseTargetUsers(targetUsersStr?: string | null): string[] { | ||
if (!targetUsersStr?.trim()) { | ||
return []; | ||
} | ||
return targetUsersStr | ||
.split(",") | ||
.map((user) => user.trim()) | ||
.filter(Boolean); | ||
} | ||
|
||
function safeParseInt( | ||
value: string | undefined | null, | ||
defaultValue: number | ||
|
@@ -106,135 +67,65 @@ function safeParseInt( | |
* Validates or constructs a TwitterConfig object using zod, | ||
* taking values from the IAgentRuntime or process.env as needed. | ||
*/ | ||
// This also is organized to serve as a point of documentation for the client | ||
// most of the inputs from the framework (env/character) | ||
|
||
// we also do a lot of typing/parsing here | ||
// so we can do it once and only once per character | ||
export async function validateTwitterConfig( | ||
runtime: IAgentRuntime | ||
): Promise<TwitterConfig> { | ||
try { | ||
const twitterConfig = { | ||
TWITTER_DRY_RUN: | ||
parseBooleanFromText( | ||
runtime.getSetting("TWITTER_DRY_RUN") || | ||
process.env.TWITTER_DRY_RUN | ||
) ?? false, // parseBooleanFromText return null if "", map "" to false | ||
|
||
TWITTER_USERNAME: | ||
runtime.getSetting("TWITTER_USERNAME") || | ||
process.env.TWITTER_USERNAME, | ||
|
||
TWITTER_PASSWORD: | ||
runtime.getSetting("TWITTER_PASSWORD") || | ||
process.env.TWITTER_PASSWORD, | ||
|
||
TWITTER_EMAIL: | ||
runtime.getSetting("TWITTER_EMAIL") || | ||
process.env.TWITTER_EMAIL, | ||
|
||
// number as string? | ||
MAX_TWEET_LENGTH: safeParseInt( | ||
runtime.getSetting("MAX_TWEET_LENGTH") || | ||
process.env.MAX_TWEET_LENGTH, | ||
DEFAULT_MAX_TWEET_LENGTH | ||
), | ||
|
||
TWITTER_SEARCH_ENABLE: | ||
parseBooleanFromText( | ||
runtime.getSetting("TWITTER_SEARCH_ENABLE") || | ||
process.env.TWITTER_SEARCH_ENABLE | ||
) ?? false, | ||
|
||
// string passthru | ||
TWITTER_2FA_SECRET: | ||
runtime.getSetting("TWITTER_2FA_SECRET") || | ||
process.env.TWITTER_2FA_SECRET || | ||
"", | ||
|
||
// int | ||
TWITTER_RETRY_LIMIT: safeParseInt( | ||
runtime.getSetting("TWITTER_RETRY_LIMIT") || | ||
process.env.TWITTER_RETRY_LIMIT, | ||
5 | ||
), | ||
|
||
// int in seconds | ||
TWITTER_POLL_INTERVAL: safeParseInt( | ||
runtime.getSetting("TWITTER_POLL_INTERVAL") || | ||
process.env.TWITTER_POLL_INTERVAL, | ||
120 // 2m | ||
), | ||
|
||
// comma separated string | ||
TWITTER_TARGET_USERS: parseTargetUsers( | ||
runtime.getSetting("TWITTER_TARGET_USERS") || | ||
process.env.TWITTER_TARGET_USERS | ||
), | ||
|
||
// int in minutes | ||
POST_INTERVAL_MIN: safeParseInt( | ||
runtime.getSetting("POST_INTERVAL_MIN") || | ||
process.env.POST_INTERVAL_MIN, | ||
90 // 1.5 hours | ||
), | ||
|
||
// int in minutes | ||
POST_INTERVAL_MAX: safeParseInt( | ||
runtime.getSetting("POST_INTERVAL_MAX") || | ||
process.env.POST_INTERVAL_MAX, | ||
180 // 3 hours | ||
), | ||
|
||
// bool | ||
ENABLE_ACTION_PROCESSING: | ||
parseBooleanFromText( | ||
runtime.getSetting("ENABLE_ACTION_PROCESSING") || | ||
process.env.ENABLE_ACTION_PROCESSING | ||
) ?? false, | ||
|
||
// init in minutes (min 1m) | ||
ACTION_INTERVAL: safeParseInt( | ||
runtime.getSetting("ACTION_INTERVAL") || | ||
process.env.ACTION_INTERVAL, | ||
5 // 5 minutes | ||
), | ||
|
||
// bool | ||
POST_IMMEDIATELY: | ||
parseBooleanFromText( | ||
runtime.getSetting("POST_IMMEDIATELY") || | ||
process.env.POST_IMMEDIATELY | ||
) ?? false, | ||
|
||
TWITTER_SPACES_ENABLE: | ||
parseBooleanFromText( | ||
runtime.getSetting("TWITTER_SPACES_ENABLE") || | ||
process.env.TWITTER_SPACES_ENABLE | ||
) ?? false, | ||
|
||
MAX_ACTIONS_PROCESSING: safeParseInt( | ||
runtime.getSetting("MAX_ACTIONS_PROCESSING") || | ||
process.env.MAX_ACTIONS_PROCESSING, | ||
1 | ||
), | ||
// Helper to handle $ prefixed values | ||
const getConfigValue = (key: string) => { | ||
const value = runtime.getSetting(key); | ||
return value?.startsWith('$') ? process.env[key] : value || process.env[key]; | ||
}; | ||
|
||
const config = { | ||
TWITTER_DRY_RUN: parseBooleanFromText(getConfigValue("TWITTER_DRY_RUN") || "false"), | ||
TWITTER_USERNAME: getConfigValue("TWITTER_USERNAME"), | ||
TWITTER_PASSWORD: getConfigValue("TWITTER_PASSWORD"), | ||
TWITTER_EMAIL: getConfigValue("TWITTER_EMAIL"), | ||
MAX_TWEET_LENGTH: safeParseInt(getConfigValue("MAX_TWEET_LENGTH"), DEFAULT_MAX_TWEET_LENGTH), | ||
TWITTER_SEARCH_ENABLE: parseBooleanFromText(getConfigValue("TWITTER_SEARCH_ENABLE") || "false"), | ||
TWITTER_2FA_SECRET: getConfigValue("TWITTER_2FA_SECRET") || "", | ||
TWITTER_RETRY_LIMIT: safeParseInt(getConfigValue("TWITTER_RETRY_LIMIT"), 5), | ||
TWITTER_POLL_INTERVAL: safeParseInt(getConfigValue("TWITTER_POLL_INTERVAL"), 60), | ||
TWITTER_TARGET_USERS: [], | ||
POST_INTERVAL_MIN: safeParseInt(getConfigValue("POST_INTERVAL_MIN"), 60), | ||
POST_INTERVAL_MAX: safeParseInt(getConfigValue("POST_INTERVAL_MAX"), 120), | ||
ENABLE_ACTION_PROCESSING: parseBooleanFromText(getConfigValue("ENABLE_ACTION_PROCESSING") || "false"), | ||
ACTION_INTERVAL: safeParseInt(getConfigValue("ACTION_INTERVAL"), 60), | ||
POST_IMMEDIATELY: parseBooleanFromText(getConfigValue("POST_IMMEDIATELY") || "false"), | ||
TWITTER_SPACES_ENABLE: parseBooleanFromText(getConfigValue("TWITTER_SPACES_ENABLE") || "false"), | ||
MAX_ACTIONS_PROCESSING: safeParseInt(getConfigValue("MAX_ACTIONS_PROCESSING"), 10), | ||
ACTION_TIMELINE_TYPE: ActionTimelineType.ForYou, | ||
}; | ||
|
||
elizaLogger.error("Raw Twitter config values:", { | ||
runtimeUsername: runtime.getSetting("TWITTER_USERNAME"), | ||
envUsername: process.env.TWITTER_USERNAME, | ||
runtimeEmail: runtime.getSetting("TWITTER_EMAIL"), | ||
envEmail: process.env.TWITTER_EMAIL, | ||
finalEmail: config.TWITTER_EMAIL | ||
}); | ||
|
||
elizaLogger.error("Twitter config validation:", { | ||
username: config.TWITTER_USERNAME, | ||
email: config.TWITTER_EMAIL, | ||
hasPassword: !!config.TWITTER_PASSWORD, | ||
has2FA: !!config.TWITTER_2FA_SECRET, | ||
searchEnabled: config.TWITTER_SEARCH_ENABLE, | ||
spacesEnabled: config.TWITTER_SPACES_ENABLE | ||
}); | ||
|
||
// Validation | ||
if (!config.TWITTER_EMAIL?.includes("@")) { | ||
throw new Error("X/Twitter configuration validation failed:\nTWITTER_EMAIL: Email must contain @ and a domain (e.g. [email protected])"); | ||
} | ||
|
||
ACTION_TIMELINE_TYPE: | ||
runtime.getSetting("ACTION_TIMELINE_TYPE") || | ||
process.env.ACTION_TIMELINE_TYPE, | ||
}; | ||
if (!config.TWITTER_USERNAME) { | ||
throw new Error("X/Twitter configuration validation failed:\nTWITTER_USERNAME is required"); | ||
} | ||
|
||
return twitterEnvSchema.parse(twitterConfig); | ||
} catch (error) { | ||
if (error instanceof ZodError) { | ||
const errorMessages = error.errors | ||
.map((err) => `${err.path.join(".")}: ${err.message}`) | ||
.join("\n"); | ||
throw new Error( | ||
`X/Twitter configuration validation failed:\n${errorMessages}` | ||
); | ||
} | ||
throw error; | ||
if (!config.TWITTER_PASSWORD) { | ||
throw new Error("X/Twitter configuration validation failed:\nTWITTER_PASSWORD is required"); | ||
} | ||
|
||
return config; | ||
} |