@@ -25,7 +25,7 @@ async function getAuthClient(): Promise<Client> {
2525 const oauth2Client = new auth . OAuth2User ( {
2626 client_id : process . env . TWITTER_CLIENT_ID as string ,
2727 client_secret : process . env . TWITTER_CLIENT_SECRET as string ,
28- callback : 'http ://www.capsulethis .com/api/x-bot/auth/callback' ,
28+ callback : 'https ://reactnativevibecode .com/api/x-bot/auth/callback' ,
2929 scopes : [ 'tweet.read' , 'tweet.write' , 'users.read' , 'offline.access' ] ,
3030 } )
3131
@@ -35,7 +35,6 @@ async function getAuthClient(): Promise<Client> {
3535
3636 await oauth2Client . refreshAccessToken ( )
3737
38- // Log if token was rotated
3938 if (
4039 oauth2Client . token ?. refresh_token &&
4140 oauth2Client . token . refresh_token !== refreshToken
@@ -49,6 +48,42 @@ async function getAuthClient(): Promise<Client> {
4948 return new Client ( oauth2Client )
5049}
5150
51+ /**
52+ * Build the final reply text within Twitter's 280 character limit.
53+ * Includes app title, brief description, edit link, and remix link.
54+ */
55+ function buildReplyText (
56+ title : string ,
57+ appDescription : string | null ,
58+ projectId : string
59+ ) : string {
60+ const baseUrl =
61+ process . env . NEXT_PUBLIC_APP_URL || 'https://reactnativevibecode.com'
62+ const editUrl = `${ baseUrl } /p/${ projectId } `
63+ const remixUrl = `${ baseUrl } /p/${ projectId } /remix`
64+
65+ const links = `\nEdit your app: ${ editUrl } \nRemix it: ${ remixUrl } `
66+ const header = `Your app "${ title } " is ready!`
67+
68+ // Calculate remaining space for description
69+ // 280 char limit, account for header + links + newlines
70+ const fixedLength = header . length + links . length + 2 // 2 for newlines between sections
71+ const maxDescLength = 280 - fixedLength
72+
73+ if ( ! appDescription || maxDescLength <= 0 ) {
74+ const text = `${ header } ${ links } `
75+ return text . length <= 280 ? text : text . substring ( 0 , 280 )
76+ }
77+
78+ const desc =
79+ appDescription . length > maxDescLength
80+ ? appDescription . substring ( 0 , maxDescLength - 3 ) + '...'
81+ : appDescription
82+
83+ const text = `${ header } \n\n${ desc } ${ links } `
84+ return text . length <= 280 ? text : `${ header } ${ links } `
85+ }
86+
5287export async function POST ( request : NextRequest ) {
5388 try {
5489 const body : ReplyRequest = await request . json ( )
@@ -66,7 +101,9 @@ export async function POST(request: NextRequest) {
66101 )
67102 }
68103
69- console . log ( `[X-Bot Reply] Sending reply for tweet ${ tweetId } , project ${ projectId } ` )
104+ console . log (
105+ `[X-Bot Reply] Sending final reply for tweet ${ tweetId } , project ${ projectId } `
106+ )
70107
71108 // Get project details
72109 const projectResults = await db
@@ -81,23 +118,36 @@ export async function POST(request: NextRequest) {
81118
82119 const project = projectResults [ 0 ]
83120
84- // Build reply text
85- const baseUrl = process . env . NEXT_PUBLIC_APP_URL || 'https://capsulethis.com'
86- const projectUrl = `${ baseUrl } /p/${ projectId } `
121+ // Get xBotReplies record to find firstReplyTweetId and appDescription
122+ const replyRecord = await db
123+ . select ( )
124+ . from ( xBotReplies )
125+ . where ( eq ( xBotReplies . tweetId , tweetId ) )
126+ . limit ( 1 )
87127
88- const replyText = `Your app "${ project . title } " is ready! 🚀\n\nView and edit: ${ projectUrl } `
128+ // Determine which tweet to reply to for proper threading
129+ // Reply to the first reply tweet to create a thread, fallback to original tweet
130+ const replyToTweetId = replyRecord [ 0 ] ?. firstReplyTweetId || tweetId
131+
132+ // Build reply text with app details
133+ const appDescription = replyRecord [ 0 ] ?. appDescription || null
134+ const replyText = buildReplyText (
135+ project . title || 'Untitled App' ,
136+ appDescription ,
137+ projectId
138+ )
89139
90140 // Send reply via Twitter API
91141 const client = await getAuthClient ( )
92142 const response = await client . tweets . createTweet ( {
93143 text : replyText ,
94- reply : { in_reply_to_tweet_id : tweetId } ,
144+ reply : { in_reply_to_tweet_id : replyToTweetId } ,
95145 } )
96146
97147 console . log ( `[X-Bot Reply] Response:` , JSON . stringify ( response ) )
98148
99149 if ( response . data ?. id ) {
100- // Update xBotReplies with reply info
150+ // Update xBotReplies with final reply info
101151 await db
102152 . update ( xBotReplies )
103153 . set ( {
@@ -116,7 +166,9 @@ export async function POST(request: NextRequest) {
116166 replyText,
117167 } )
118168 } else {
119- console . error ( `[X-Bot Reply] No reply ID returned for tweet ${ tweetId } ` )
169+ console . error (
170+ `[X-Bot Reply] No reply ID returned for tweet ${ tweetId } `
171+ )
120172
121173 await db
122174 . update ( xBotReplies )
0 commit comments