Proxy Telegram bot user avatars
All checks were successful
Build and deploy Backend / build (push) Successful in 31s
All checks were successful
Build and deploy Backend / build (push) Successful in 31s
This commit is contained in:
@@ -25,6 +25,14 @@ type TelegramBotUser = {
|
||||
language_code?: string;
|
||||
};
|
||||
|
||||
type TelegramProfilePhotos = {
|
||||
photos: { file_id: string }[][];
|
||||
};
|
||||
|
||||
type TelegramFile = {
|
||||
file_path: string;
|
||||
};
|
||||
|
||||
const loginPrefix = 'login_';
|
||||
|
||||
function randomToken() {
|
||||
@@ -43,7 +51,7 @@ function botApiUrl(method: string) {
|
||||
return `https://api.telegram.org/bot${config.telegramMiniAppBotToken}/${method}`;
|
||||
}
|
||||
|
||||
async function callTelegram(method: string, body: Record<string, unknown>) {
|
||||
async function callTelegram<T>(method: string, body: Record<string, unknown>) {
|
||||
const response = await fetch(botApiUrl(method), {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
@@ -54,10 +62,16 @@ async function callTelegram(method: string, body: Record<string, unknown>) {
|
||||
throw new Error(`Telegram ${method} failed with ${response.status}.`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as { ok: boolean; description?: string };
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
description?: string;
|
||||
result?: T;
|
||||
};
|
||||
if (!payload.ok) {
|
||||
throw new Error(payload.description ?? `Telegram ${method} failed.`);
|
||||
}
|
||||
|
||||
return payload.result as T;
|
||||
}
|
||||
|
||||
function userPayload(from: TelegramBotUser): TelegramUserPayload {
|
||||
@@ -70,6 +84,32 @@ function userPayload(from: TelegramBotUser): TelegramUserPayload {
|
||||
};
|
||||
}
|
||||
|
||||
async function telegramUserPhotoUrl(userId: number) {
|
||||
const photos = await callTelegram<TelegramProfilePhotos>('getUserProfilePhotos', {
|
||||
user_id: userId,
|
||||
limit: 1,
|
||||
});
|
||||
const variants = photos.photos[0];
|
||||
const fileId = variants?.[variants.length - 1]?.file_id;
|
||||
return fileId ? `${config.publicApiUrl}/telegram/photo/${fileId}` : undefined;
|
||||
}
|
||||
|
||||
export async function fetchTelegramPhoto(fileId: string) {
|
||||
const file = await callTelegram<TelegramFile>('getFile', { file_id: fileId });
|
||||
const response = await fetch(
|
||||
`https://api.telegram.org/file/bot${config.telegramMiniAppBotToken}/${file.file_path}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Telegram file fetch failed with ${response.status}.`);
|
||||
}
|
||||
|
||||
return {
|
||||
contentType: response.headers.get('content-type') ?? 'image/jpeg',
|
||||
bytes: Buffer.from(await response.arrayBuffer()),
|
||||
};
|
||||
}
|
||||
|
||||
async function sendLoginMessage(chatId: number, text: string, token?: string) {
|
||||
const replyMarkup = token
|
||||
? {
|
||||
@@ -166,7 +206,10 @@ export async function handleTelegramBotWebhook(
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await upsertTelegramUser(userPayload(from));
|
||||
const user = await upsertTelegramUser({
|
||||
...userPayload(from),
|
||||
photoUrl: await telegramUserPhotoUrl(from.id),
|
||||
});
|
||||
const sessionToken = randomToken();
|
||||
await prisma.userSession.create({
|
||||
data: {
|
||||
|
||||
@@ -13,6 +13,7 @@ export const config = {
|
||||
telegramBotUsername: process.env.TELEGRAM_BOT_USERNAME ?? 'carfteebot',
|
||||
telegramWebhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET ?? '',
|
||||
webAppUrl: process.env.WEB_APP_URL ?? 'https://map.craftee.vn',
|
||||
publicApiUrl: process.env.PUBLIC_API_URL ?? 'https://api.map.craftee.vn',
|
||||
telegramAuthMaxAgeSeconds: Number(
|
||||
process.env.TELEGRAM_AUTH_MAX_AGE_SECONDS ?? '86400',
|
||||
),
|
||||
|
||||
@@ -4,7 +4,10 @@ import mercurius from 'mercurius';
|
||||
import { config } from './config.js';
|
||||
import { prisma } from './prisma.js';
|
||||
import { resolvers, schema } from './graphql/schema.js';
|
||||
import { handleTelegramBotWebhook } from './auth/telegram-bot-login.js';
|
||||
import {
|
||||
fetchTelegramPhoto,
|
||||
handleTelegramBotWebhook,
|
||||
} from './auth/telegram-bot-login.js';
|
||||
|
||||
const app = Fastify({ logger: true });
|
||||
|
||||
@@ -33,6 +36,14 @@ app.post('/telegram/webhook', async (request, reply) => {
|
||||
return reply.send({ ok: true });
|
||||
});
|
||||
|
||||
app.get('/telegram/photo/:fileId', async (request, reply) => {
|
||||
const params = request.params as { fileId: string };
|
||||
const photo = await fetchTelegramPhoto(params.fileId);
|
||||
reply.header('content-type', photo.contentType);
|
||||
reply.header('cache-control', 'public, max-age=86400');
|
||||
return reply.send(photo.bytes);
|
||||
});
|
||||
|
||||
app.addHook('onClose', async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user