Skip to content

Commit 60afd8b

Browse files
authored
fix: add support for telegram link with search query (#261)
1 parent f18637f commit 60afd8b

File tree

6 files changed

+177
-8
lines changed

6 files changed

+177
-8
lines changed

apps/example-bot/src/app/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export default async function Page({
1919
<br />
2020
Click the menu button to see the available options.
2121
</p>
22-
<code>Hello</code>
2322
</div>
2423
);
2524
}

packages/core/src/core/core.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,14 @@ export class Core<T extends Container<BaseChatroomInfo, BaseMessage>>
370370
type: "page",
371371
},
372372
);
373+
// if there is a parse search query, pass it to the component
374+
if (component.queryString) {
375+
component.queryString = {
376+
...component.queryString,
377+
...defaultRoute?.props?.searchQuery,
378+
};
379+
}
380+
373381
await this.setComponent(component);
374382
return component;
375383
}

packages/telegram-adapter/src/adapter.spec.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Menu } from "@rx-lab/common";
22
import * as TelegramBot from "node-telegram-bot-api";
33
import { TGContainer, TelegramAdapter } from "./adapter";
44
import { renderElement } from "./renderer";
5+
import { DEFAULT_ROOT_PATH } from "./types";
56

67
jest.mock("node-telegram-bot-api");
78
jest.mock("./callbackParser");
@@ -306,4 +307,126 @@ describe("TelegramAdapter", () => {
306307
expect(mockBot.setMyCommands).toHaveBeenCalledWith([]);
307308
});
308309
});
310+
311+
describe("getCurrentRoute", () => {
312+
let adapter: TelegramAdapter;
313+
314+
beforeEach(() => {
315+
adapter = new TelegramAdapter({ token: "fake-token" });
316+
});
317+
318+
it("should return undefined for messages without entities", async () => {
319+
const message = {
320+
message_id: 1,
321+
chat: { id: 123 },
322+
text: "hello world",
323+
} as TelegramBot.Message;
324+
325+
const result = await adapter.getCurrentRoute(message);
326+
expect(result).toBeUndefined();
327+
});
328+
329+
it("should return undefined for non-command entities", async () => {
330+
const message = {
331+
message_id: 1,
332+
chat: { id: 123 },
333+
text: "hello @username",
334+
entities: [
335+
{
336+
type: "mention",
337+
offset: 6,
338+
length: 9,
339+
},
340+
],
341+
} as TelegramBot.Message;
342+
343+
const result = await adapter.getCurrentRoute(message);
344+
expect(result).toBeUndefined();
345+
});
346+
347+
it("should handle /start command and return root path", async () => {
348+
const message = {
349+
message_id: 1,
350+
chat: { id: 123 },
351+
text: "/start",
352+
entities: [
353+
{
354+
type: "bot_command",
355+
offset: 0,
356+
length: 6,
357+
},
358+
],
359+
} as TelegramBot.Message;
360+
361+
const result = await adapter.getCurrentRoute(message);
362+
expect(result).toEqual({
363+
route: DEFAULT_ROOT_PATH,
364+
query: {},
365+
});
366+
});
367+
368+
it("should handle /start command with query parameters", async () => {
369+
const message = {
370+
message_id: 1,
371+
chat: { id: 123 },
372+
text: "/start param1=value1&param2=value2",
373+
entities: [
374+
{
375+
type: "bot_command",
376+
offset: 0,
377+
length: 6,
378+
},
379+
],
380+
} as TelegramBot.Message;
381+
382+
const result = await adapter.getCurrentRoute(message);
383+
expect(result).toEqual({
384+
route: DEFAULT_ROOT_PATH,
385+
query: {
386+
param1: "value1",
387+
param2: "value2",
388+
},
389+
});
390+
});
391+
392+
it("should handle other bot commands", async () => {
393+
const message = {
394+
message_id: 1,
395+
chat: { id: 123 },
396+
text: "/help",
397+
entities: [
398+
{
399+
type: "bot_command",
400+
offset: 0,
401+
length: 5,
402+
},
403+
],
404+
} as TelegramBot.Message;
405+
406+
const result = await adapter.getCurrentRoute(message);
407+
expect(result).toEqual({
408+
route: "/help",
409+
});
410+
});
411+
412+
it("should handle bot commands in group chats with bot mention", async () => {
413+
const message = {
414+
message_id: 1,
415+
chat: { id: 123 },
416+
text: "/help@mybot",
417+
entities: [
418+
{
419+
type: "bot_command",
420+
offset: 0,
421+
length: 11,
422+
},
423+
],
424+
} as TelegramBot.Message;
425+
426+
const result = await adapter.getCurrentRoute(message);
427+
expect(result).toEqual({
428+
route: "/help",
429+
});
430+
});
431+
});
309432
});

packages/telegram-adapter/src/adapter.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ interface InternalTGContainer extends TGContainer {
4949
decodedData: DecodeType;
5050
}
5151

52+
function parseQueryString(queryString: string) {
53+
const urlParams = new URLSearchParams(queryString);
54+
const entries = urlParams.entries();
55+
const result: Record<string, string> = {};
56+
for (const [key, value] of entries) {
57+
result[key] = value;
58+
}
59+
return result;
60+
}
61+
5262
export class TelegramAdapter
5363
implements
5464
AdapterInterface<
@@ -299,9 +309,13 @@ export class TelegramAdapter
299309
return route;
300310
}
301311

302-
async getCurrentRoute(
303-
message: TelegramBot.Message,
304-
): Promise<string | undefined> {
312+
async getCurrentRoute(message: TelegramBot.Message): Promise<
313+
| {
314+
route: string;
315+
query?: Record<string, string>;
316+
}
317+
| undefined
318+
> {
305319
if (!message.entities) {
306320
return;
307321
}
@@ -314,9 +328,16 @@ export class TelegramAdapter
314328
command = command?.split("@")[0];
315329

316330
if (command === START_COMMAND) {
317-
return DEFAULT_ROOT_PATH;
331+
const query = message.text?.split(" ")[1];
332+
const decodedQuery = parseQueryString(query ?? "");
333+
return {
334+
route: DEFAULT_ROOT_PATH,
335+
query: decodedQuery as Record<string, string>,
336+
};
318337
}
319-
return command;
338+
return {
339+
route: command,
340+
};
320341
}
321342
}
322343

@@ -338,8 +359,11 @@ export class TelegramAdapter
338359
const currentRoute = await this.getCurrentRoute(route);
339360
if (currentRoute) {
340361
return {
341-
route: await this.getCurrentRoute(route),
362+
route: currentRoute.route,
342363
type: "page",
364+
props: currentRoute.query
365+
? ({ searchQuery: currentRoute.query } as any)
366+
: undefined,
343367
};
344368
}
345369
}

packages/telegram-adapter/src/renderer.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const renderElementHelper = (
5151
return [`<code>${children.join("")}</code>`];
5252
case InstanceType.Command:
5353
if (element.props.variant === "button") {
54-
if (element.props.command.startsWith("http")) {
54+
if (element.props.webapp) {
5555
return {
5656
type: "button",
5757
text: children.join(""),
@@ -60,6 +60,15 @@ export const renderElementHelper = (
6060
},
6161
};
6262
}
63+
64+
if (element.props.command.startsWith("http")) {
65+
return {
66+
type: "button",
67+
text: children.join(""),
68+
url: element.props.command,
69+
};
70+
}
71+
6372
const callbackData = {
6473
route: convertRouteToTGRoute(element.props.command),
6574
new: element.props.renderNewMessage,
@@ -169,10 +178,15 @@ export const renderElement = (
169178
text: res.text,
170179
callback_data: res.callback_data,
171180
};
181+
172182
if (res.web_app) {
173183
button.web_app = res.web_app;
174184
}
175185

186+
if (res.url) {
187+
button.url = res.url;
188+
}
189+
176190
if (level === 1) {
177191
inlineKeyboardButtons.push([button]);
178192
} else {

packages/telegram-adapter/src/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function convertTGRouteToRoute(route: StoredRoute): string {
5555

5656
// Find the position of the first underscore
5757
const firstUnderscoreIndex = route.route.indexOf("_");
58+
const startPayload = route.route.split(" ")[1];
5859

5960
// If there's no underscore, return the route as is
6061
if (firstUnderscoreIndex === -1) {

0 commit comments

Comments
 (0)