@@ -22,20 +22,39 @@ import {
2222} from "../constants" ;
2323import { fetchFileFromUrl , getSentimentChart } from "../http-helper" ;
2424
25+ // Tweet type for sentiment analysis
26+ interface Tweet {
27+ id : string ;
28+ text : string ;
29+ username : string ;
30+ name : string ;
31+ userId : string ;
32+ timestamp : number ;
33+ permanentUrl : string ;
34+ conversationId : string ;
35+ inReplyToStatusId ?: string ;
36+ hashtags : string [ ] ;
37+ mentions : string [ ] ;
38+ photos : string [ ] ;
39+ urls : string [ ] ;
40+ videos : string [ ] ;
41+ views ?: number ;
42+ }
43+
2544let DkgClient : any = null ;
2645
2746// Helper to get Twitter client from runtime
2847function getTwitterClient ( runtime : IAgentRuntime ) : any {
2948 // Access the Twitter client from the runtime's clients
30- const twitterClient = ( runtime as any ) . clients ?. find (
31- ( client : any ) => client . constructor . name === "TwitterClientInterface"
32- ) ;
33-
34- if ( ! twitterClient ) {
49+ // clients is a Record<string, any> object, not an array
50+ const clients = ( runtime as any ) . clients ;
51+
52+ if ( ! clients || ! clients . twitter ) {
3553 throw new Error ( "Twitter client not found in runtime" ) ;
3654 }
3755
38- return twitterClient ;
56+ // The twitter client has a .client property with the actual API methods
57+ return clients . twitter ;
3958}
4059
4160// Helper to convert v2 API tweet format to legacy format for sentiment analysis
@@ -245,8 +264,17 @@ export const dkgAnalyzeSentiment: Action = {
245264 nodeApiVersion : "/v1" ,
246265 } ) ;
247266
248- const currentPost = String ( state . currentPost ) ;
249- elizaLogger . log ( `currentPost: ${ currentPost } ` ) ;
267+ // Get the post content - from Twitter state or from direct message
268+ let currentPost = state . currentPost ? String ( state . currentPost ) : "" ;
269+
270+ // Fallback to message content if currentPost is empty/undefined (e.g., API calls)
271+ if ( ! currentPost || currentPost === "undefined" ) {
272+ const messageText = _message . content ?. text || "" ;
273+ currentPost = `Text: ${ messageText } ` ;
274+ elizaLogger . log ( `Using message content as currentPost: ${ currentPost } ` ) ;
275+ } else {
276+ elizaLogger . log ( `currentPost: ${ currentPost } ` ) ;
277+ }
250278
251279 const idRegex = / I D : \s ( \d + ) / ;
252280 let match = currentPost . match ( idRegex ) ;
@@ -298,14 +326,30 @@ export const dkgAnalyzeSentiment: Action = {
298326 return true ;
299327 }
300328
329+ // Clean the topic for Twitter search - remove $ and # operators (require Pro tier)
330+ // but keep the original topic for display
331+ const originalTopic = topic . trim ( ) ;
332+ const searchTopic = originalTopic . replace ( / [ $ # ] / g, '' ) . trim ( ) ;
333+
334+ // Validate searchTopic is not empty after stripping symbols
335+ if ( ! searchTopic || searchTopic . length < 2 ) {
336+ elizaLogger . warn ( `Search topic too short after cleaning: "${ searchTopic } " (original: "${ originalTopic } ")` ) ;
337+ callback ?.( {
338+ text : `I couldn't identify a valid ticker or asset name. Please specify a stock (like AAPL, TSLA) or cryptocurrency (like BTC, ETH) to analyze.` ,
339+ action : "REPLY"
340+ } ) ;
341+ return true ;
342+ }
343+
301344 // Use OAuth v2 search (already implemented in client-twitter)
302- elizaLogger . log ( `Searching for tweets about: ${ topic } ` ) ;
345+ elizaLogger . log ( `Searching for tweets about: ${ searchTopic } (original: ${ originalTopic } ) ` ) ;
303346
304347 let searchResults ;
305348 try {
306349 // Access the client's fetchSearchTweets method
350+ // Use searchTopic (without $ or #) to avoid cashtag/hashtag operator issues on Basic tier
307351 searchResults = await twitterClient . client . fetchSearchTweets (
308- topic ,
352+ searchTopic ,
309353 100 ,
310354 2 // SearchMode.Latest
311355 ) ;
@@ -321,6 +365,16 @@ export const dkgAnalyzeSentiment: Action = {
321365 let tweets = searchResults . tweets . map ( convertTweetFormat ) ;
322366 elizaLogger . log ( `Successfully fetched ${ tweets . length } tweets.` ) ;
323367
368+ // Handle case where no tweets are found
369+ if ( ! tweets || tweets . length === 0 ) {
370+ elizaLogger . warn ( `No tweets found for topic: ${ searchTopic } ` ) ;
371+ callback ?.( {
372+ text : `I couldn't find any recent tweets about ${ originalTopic } . This could mean there's very little discussion about this asset right now, or try a different ticker/name.` ,
373+ action : "REPLY"
374+ } ) ;
375+ return true ;
376+ }
377+
324378 tweets = tweets . map ( ( t ) => ( {
325379 ...t ,
326380 vaderSentimentScore : calculateVaderScore ( t . text ) ,
@@ -332,7 +386,7 @@ export const dkgAnalyzeSentiment: Action = {
332386
333387 const { ka, averageScore, numOfTotalTweets } = await structureKA (
334388 tweets ,
335- topic ,
389+ originalTopic ,
336390 twitterUser ,
337391 {
338392 dkgClient : DkgClient ,
@@ -411,13 +465,13 @@ export const dkgAnalyzeSentiment: Action = {
411465 elizaLogger . log ( JSON . stringify ( createAssetResult ) ) ;
412466
413467 // Build the sentiment analysis response
414- let tweetContent = `${ topic } sentiment based on top ${ tweets . length } latest posts` ;
468+ let tweetContent = `${ originalTopic } sentiment based on top ${ tweets . length } latest posts` ;
415469 if ( numOfTotalTweets - tweets . length > 0 ) {
416470 tweetContent += ` and ${ numOfTotalTweets - tweets . length } existing analysis Knowledge Assets` ;
417471 }
418472 tweetContent += ` from the past 48 hours: ${ sentiment } \n\n` ;
419473
420- tweetContent += `Top 5 most influential accounts analyzed for ${ topic } :\n` ;
474+ tweetContent += `Top 5 most influential accounts analyzed for ${ originalTopic } :\n` ;
421475 tweetContent +=
422476 topAuthors
423477 . slice ( 0 , 5 )
0 commit comments