@@ -13,6 +13,8 @@ import {
13
13
import { trackEvent } from "../helpers/analytics" ;
14
14
import { notifyAdmin } from "../helpers/notifier" ;
15
15
import { getOGMetadata } from "../helpers/og-metadata" ;
16
+ import { saveToCache , deleteFromCache } from "../helpers/cache" ;
17
+ import { getButtonState } from "../helpers/button-states" ;
16
18
17
19
type UserInfoType = {
18
20
username : string | undefined ;
@@ -177,127 +179,130 @@ export async function expandLink(
177
179
) ;
178
180
}
179
181
} else {
180
- botReply = await ctx . api . sendMessage (
181
- chatId ,
182
- await expandedMessageTemplate (
183
- ctx ,
184
- userInfo . username ,
185
- userInfo . userId ,
186
- userInfo . firstName ,
187
- userInfo . lastName ,
188
- messageText ,
189
- linkWithNoTrackers
190
- ) ,
191
- {
192
- ...replyOptions ,
193
- // Use HTML parse mode if the user does not have a username,
194
- // otherwise the bot will not be able to mention the user.
195
- parse_mode : "HTML" ,
196
- reply_markup : {
197
- inline_keyboard : [
198
- [
199
- {
200
- text : "❌ Delete — 15s" ,
201
- callback_data : `destruct:${ userInfo . userId } :${ expansionType } ` ,
202
- } ,
203
- ...( linkWithNoTrackers . includes ( "fxtwitter" )
204
- ? [
205
- {
206
- text : "🔗 Open on Twitter" ,
207
- url : link ?. replace ( "fxtwitter" , "twitter" ) ,
208
- } ,
209
- ]
210
- : [ ] ) ,
211
- ] ,
212
- ] ,
213
- } ,
214
- }
182
+ // For all other platforms
183
+ const template = await expandedMessageTemplate (
184
+ ctx ,
185
+ userInfo . username ,
186
+ userInfo . userId ,
187
+ userInfo . firstName ,
188
+ userInfo . lastName ,
189
+ messageText ,
190
+ linkWithNoTrackers
215
191
) ;
216
- }
217
192
218
- if ( topicId ) {
219
- trackEvent ( `expand.${ expansionType } .inside-topic` ) ;
220
- }
193
+ // Add buttons based on platform
194
+ let platform : "twitter" | "instagram" | "tiktok" | null = null ;
195
+ if ( isInstagram ( link ) ) platform = "instagram" ;
196
+ else if ( isTikTok ( link ) ) platform = "tiktok" ;
197
+ else if ( isTweet ( link ) ) platform = "twitter" ;
221
198
222
- // Countdown the destruct timer under message
223
- if ( botReply ) {
224
- async function editDestructTimer ( time : number ) {
225
- try {
226
- await ctx . api
227
- . editMessageReplyMarkup ( botReply . chat . id , botReply . message_id , {
228
- reply_markup : {
229
- inline_keyboard : [
230
- [
231
- {
232
- text : `❌ Delete — ${ time } s` ,
233
- callback_data : `destruct:${ userInfo . userId } :${ expansionType } ` ,
234
- } ,
235
- ...( linkWithNoTrackers . includes ( "fxtwitter" )
236
- ? [
237
- {
238
- text : "🔗 Open on Twitter" ,
239
- url : link ?. replace ( "fxtwitter" , "twitter" ) ,
240
- } ,
241
- ]
242
- : [ ] ) ,
243
- ] ,
244
- ] ,
245
- } ,
246
- } )
247
- . catch ( ( ) => {
248
- console . error (
249
- "[Error] [expand-link.ts:113] Could not edit destruct timer. Message was probably deleted."
250
- ) ;
251
- return ;
252
- } ) ;
253
- } catch ( error ) {
254
- // console.error("[Error] Could not edit destruct timer. Message was probably deleted.");
255
- // @ts -ignore
256
- // console.error(error.message);
257
- return ;
258
- }
259
- }
199
+ const replyMarkup = platform
200
+ ? {
201
+ inline_keyboard : getButtonState ( platform , 15 , userInfo . userId || ctx . from ?. id || 0 , link ) . buttons ,
202
+ }
203
+ : undefined ;
260
204
261
- // edit timer to 10s
262
- setTimeout ( async ( ) => {
263
- editDestructTimer ( 10 ) ;
264
- } , 5000 ) ;
205
+ try {
206
+ botReply = await ctx . api . sendMessage ( chatId , template , {
207
+ parse_mode : "HTML" ,
208
+ ...replyOptions ,
209
+ ...( replyMarkup ? { reply_markup : replyMarkup } : { } ) ,
210
+ } ) ;
265
211
266
- // edit timer to 5s
267
- setTimeout ( async ( ) => {
268
- editDestructTimer ( 5 ) ;
269
- } , 10000 ) ;
212
+ // For supported platforms, start the button progression
213
+ if ( platform && botReply ) {
214
+ const identifier = ` ${ chatId } : ${ botReply . message_id } ` ;
215
+ await saveToCache ( identifier , ctx ) ;
270
216
271
- // clear buttons after 15s
272
- setTimeout ( async ( ) => {
273
- try {
274
- await ctx . api
275
- . editMessageReplyMarkup ( botReply . chat . id , botReply . message_id , {
276
- reply_markup : {
277
- inline_keyboard : [
278
- [
279
- ...( linkWithNoTrackers . includes ( "fxtwitter" )
280
- ? [
281
- {
282
- text : "🔗 Open on Twitter" ,
283
- url : link ?. replace ( "fxtwitter" , "twitter" ) ,
284
- } ,
285
- ]
286
- : [ ] ) ,
287
- ] ,
288
- ] ,
289
- } ,
290
- } )
291
- . catch ( ( ) => {
292
- console . error (
293
- "[Error] [expand-link.ts:144] Could not clear destruct timer. Message was probably deleted."
294
- ) ;
295
- } ) ;
296
- } catch ( error ) {
297
- // console.error("[Error] Could not clear destruct timer. Message was probably deleted.");
298
- return ;
217
+ // Keep track of timeouts so we can clear them if needed
218
+ const timeouts : NodeJS . Timeout [ ] = [ ] ;
219
+
220
+ // Start the button progression
221
+ const updateButtons = async ( timeRemaining : number ) => {
222
+ try {
223
+ const state = getButtonState ( platform ! , timeRemaining , userInfo . userId || ctx . from ?. id || 0 , link ) ;
224
+ await ctx . api . editMessageReplyMarkup ( chatId , botReply ! . message_id , {
225
+ reply_markup : { inline_keyboard : state . buttons } ,
226
+ } ) ;
227
+
228
+ // Schedule next update if there is one
229
+ if ( state . nextTimeout !== null ) {
230
+ const timeout = setTimeout ( ( ) => {
231
+ try {
232
+ updateButtons ( state . nextTimeout ! ) . catch ( ( ) => {
233
+ // Clear all timeouts if we can't update buttons
234
+ timeouts . forEach ( ( t ) => clearTimeout ( t ) ) ;
235
+ } ) ;
236
+ } catch ( error ) {
237
+ // Clear all timeouts if we can't update buttons
238
+ timeouts . forEach ( ( t ) => clearTimeout ( t ) ) ;
239
+ }
240
+ } , 5000 ) ;
241
+ timeouts . push ( timeout ) ;
242
+ }
243
+ } catch ( error ) {
244
+ // Message not found errors are expected if message was deleted
245
+ if ( error instanceof Error && error . message . includes ( "message to edit not found" ) ) {
246
+ console . warn ( "[Warning] Message has probably been already deleted." ) ;
247
+ // Clear all timeouts since we can't update this message anymore
248
+ timeouts . forEach ( ( t ) => clearTimeout ( t ) ) ;
249
+ } else {
250
+ console . error ( "[Error] Failed to update buttons:" , error ) ;
251
+ }
252
+ }
253
+ } ;
254
+
255
+ // Start the progression
256
+ try {
257
+ const initialTimeout = setTimeout ( ( ) => {
258
+ updateButtons ( 10 ) . catch ( ( ) => {
259
+ // Clear all timeouts if initial update fails
260
+ timeouts . forEach ( ( t ) => clearTimeout ( t ) ) ;
261
+ } ) ;
262
+ } , 5000 ) ;
263
+ timeouts . push ( initialTimeout ) ;
264
+
265
+ // Remove from cache and set final state after 35 seconds
266
+ const finalTimeout = setTimeout ( ( ) => {
267
+ try {
268
+ deleteFromCache ( identifier ) ;
269
+ // Set final state with just the open button
270
+ const finalState = getButtonState ( platform ! , null , userInfo . userId || ctx . from ?. id || 0 , link ) ;
271
+ ctx . api
272
+ . editMessageReplyMarkup ( chatId , botReply ! . message_id , {
273
+ reply_markup : { inline_keyboard : finalState . buttons } ,
274
+ } )
275
+ . catch ( ( error ) => {
276
+ if ( error . message . includes ( "message to edit not found" ) ) {
277
+ console . warn ( "[Warning] Message has probably been already deleted." ) ;
278
+ } else {
279
+ console . error ( "[Error] Failed to set final button state:" , error ) ;
280
+ }
281
+ } ) ;
282
+ } catch ( error ) {
283
+ // Message not found errors are expected if message was deleted
284
+ if ( error instanceof Error && error . message . includes ( "message to edit not found" ) ) {
285
+ console . warn ( "[Warning] Message has probably been already deleted." ) ;
286
+ } else {
287
+ console . error ( "[Error] Failed to set final button state:" , error ) ;
288
+ }
289
+ }
290
+ } , 35000 ) ;
291
+ timeouts . push ( finalTimeout ) ;
292
+ } catch ( error ) {
293
+ console . error ( "[Error] Failed to start button progression:" , error ) ;
294
+ // Clear any timeouts that might have been set
295
+ timeouts . forEach ( ( t ) => clearTimeout ( t ) ) ;
296
+ }
299
297
}
300
- } , 15000 ) ;
298
+ } catch ( error ) {
299
+ console . error ( "[Error] Could not reply with an expanded link." , error ) ;
300
+ throw error ;
301
+ }
302
+ }
303
+
304
+ if ( topicId ) {
305
+ trackEvent ( `expand.${ expansionType } .inside-topic` ) ;
301
306
}
302
307
} catch ( error ) {
303
308
console . error ( "[Error: expand-link.ts] Could not reply with an expanded link." ) ;
0 commit comments