Skip to content

Commit 07aae38

Browse files
committed
enable reels, undo expansion for tw/ig/tiktok
fixed #61
1 parent c2e351a commit 07aae38

File tree

10 files changed

+388
-131
lines changed

10 files changed

+388
-131
lines changed

actions/expand-link.ts

+119-114
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
import { trackEvent } from "../helpers/analytics";
1414
import { notifyAdmin } from "../helpers/notifier";
1515
import { getOGMetadata } from "../helpers/og-metadata";
16+
import { saveToCache, deleteFromCache } from "../helpers/cache";
17+
import { getButtonState } from "../helpers/button-states";
1618

1719
type UserInfoType = {
1820
username: string | undefined;
@@ -177,127 +179,130 @@ export async function expandLink(
177179
);
178180
}
179181
} 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
215191
);
216-
}
217192

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";
221198

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;
260204

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+
});
265211

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);
270216

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+
}
299297
}
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`);
301306
}
302307
} catch (error) {
303308
console.error("[Error: expand-link.ts] Could not reply with an expanded link.");

callbacks/destruct-expanded-link.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Context } from "grammy";
22
import { deleteMessage } from "../actions/delete-message";
33
import { trackEvent } from "../helpers/analytics";
4+
import { getButtonState } from "../helpers/button-states";
45

56
/**
6-
* Handle responses to expanded links "❌ Delete" button
7+
* Handle responses to expanded link's "❌ Delete" button
78
* @param ctx Telegram context
89
*/
910
export async function handleExpandedLinkDestruction(ctx: Context) {
@@ -18,7 +19,7 @@ export async function handleExpandedLinkDestruction(ctx: Context) {
1819
if (data.includes("destruct:")) {
1920
const destructData = data.split(":");
2021
const originalAuthorId = Number(destructData[1]);
21-
const expansionType = destructData[2];
22+
const timeRemaining = Number(destructData[2]);
2223
const answerGiverId = answer?.from?.id;
2324

2425
if (answerGiverId !== originalAuthorId) {
@@ -32,7 +33,7 @@ export async function handleExpandedLinkDestruction(ctx: Context) {
3233
return;
3334
});
3435

35-
trackEvent(`destruct.${expansionType}.not-author-alert`);
36+
trackEvent(`destruct.${timeRemaining}.not-author-alert`);
3637
return;
3738
}
3839

@@ -41,7 +42,7 @@ export async function handleExpandedLinkDestruction(ctx: Context) {
4142
console.error(`[Error] Cannot answer callback query.`);
4243
return;
4344
});
44-
trackEvent(`destruct.${expansionType}.author`);
45+
trackEvent(`destruct.${timeRemaining}.author`);
4546
return;
4647
}
4748
}

callbacks/index.ts

+44-11
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { Context } from "grammy";
22
import { bot } from "..";
33
import { getMemberCount } from "../actions/get-member-count";
44
import { handleManualExpand } from "./manual-expand";
5+
import { handleExpandedLinkDestruction } from "./destruct-expanded-link";
56
import { handleAutoexpandSettings } from "./settings-autoexpand";
7+
import { handleLockSettings } from "./settings-lock";
68
import { handleChangelogSettings } from "./settings-changelog";
79
import { handlePermissionsSettings } from "./settings-permissions";
8-
import { handleLockSettings } from "./settings-lock";
9-
import { handleExpandedLinkDestruction } from "./destruct-expanded-link";
10+
import { handleUndo } from "./undo";
1011
import { isBanned } from "../helpers/banned";
1112

1213
// Multiple bot.on("callback_query") functions cannot run in parallel.
@@ -15,18 +16,50 @@ import { isBanned } from "../helpers/banned";
1516
// that are specific to the callback_query data to keep it more clean.
1617
bot.on("callback_query", async (ctx: Context) => {
1718
const chatId = ctx.update?.callback_query?.message?.chat.id;
19+
const data = ctx.update?.callback_query?.data;
1820

19-
if (!chatId) return;
21+
if (!chatId || !data) return;
2022
if (isBanned(chatId)) return;
2123

22-
// Handle specific callbacks / button presses
23-
handleManualExpand(ctx);
24-
handleExpandedLinkDestruction(ctx);
25-
handleAutoexpandSettings(ctx);
26-
handleChangelogSettings(ctx);
27-
handlePermissionsSettings(ctx);
28-
handleLockSettings(ctx);
29-
3024
// Save chat member count to database
3125
getMemberCount(chatId);
26+
27+
// Handle expand callbacks
28+
if (data.includes("expand:")) {
29+
await handleManualExpand(ctx);
30+
return;
31+
}
32+
33+
// Handle destruct callbacks
34+
if (data.includes("destruct:")) {
35+
await handleExpandedLinkDestruction(ctx);
36+
return;
37+
}
38+
39+
// Handle undo callbacks
40+
if (data === "undo") {
41+
await handleUndo(ctx);
42+
return;
43+
}
44+
45+
// Handle settings callbacks
46+
if (data.includes("settings:autoexpand")) {
47+
await handleAutoexpandSettings(ctx);
48+
return;
49+
}
50+
51+
if (data.includes("settings:lock")) {
52+
await handleLockSettings(ctx);
53+
return;
54+
}
55+
56+
if (data.includes("settings:changelog")) {
57+
await handleChangelogSettings(ctx);
58+
return;
59+
}
60+
61+
if (data.includes("settings:permissions")) {
62+
await handlePermissionsSettings(ctx);
63+
return;
64+
}
3265
});

0 commit comments

Comments
 (0)