fix: send photos from disk instead of URL - no ADMIN_URL needed
sendPhoto now sends local files from /app/uploads/ instead of requiring a publicly accessible URL. This fixes the issue where onion addresses and private IPs are unreachable by Telegram API servers. - resolvePhotoSource(): http URLs pass through, relative paths resolved to local file path in uploads dir - sendProductPhoto(): sends file directly, falls back to corrupt-photo.jpg - Removed all ADMIN_URL prefix logic for photo URLs - Works without any public IP or domain
This commit is contained in:
@@ -4,6 +4,32 @@ import userStates from '../../../context/userStates.js';
|
||||
import LocationService from '../../../services/locationService.js';
|
||||
import ProductService from '../../../services/productService.js';
|
||||
import logger from '../../../utils/logger.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
|
||||
const FALLBACK_PHOTO = path.join(process.cwd(), 'corrupt-photo.jpg');
|
||||
|
||||
function resolvePhotoSource(photoUrl) {
|
||||
if (!photoUrl) return null;
|
||||
if (photoUrl.startsWith('http')) return photoUrl;
|
||||
const filePath = path.join(UPLOADS_DIR, photoUrl.replace(/^\/uploads\//, ''));
|
||||
if (fs.existsSync(filePath)) return filePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function sendProductPhoto(chatId, photoUrl, caption) {
|
||||
const source = resolvePhotoSource(photoUrl);
|
||||
if (!source) return null;
|
||||
try {
|
||||
return await bot.sendPhoto(chatId, source, { caption });
|
||||
} catch (e) {
|
||||
if (fs.existsSync(FALLBACK_PHOTO)) {
|
||||
return await bot.sendPhoto(chatId, FALLBACK_PHOTO, { caption });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default class ViewHandler {
|
||||
|
||||
@@ -62,24 +88,14 @@ export default class ViewHandler {
|
||||
let hiddenPhotoMessage;
|
||||
|
||||
if (product.photo_url) {
|
||||
const photoUrl = product.photo_url.startsWith('http') ? product.photo_url : `${process.env.ADMIN_URL}${product.photo_url}`;
|
||||
try {
|
||||
photoMessage = await bot.sendPhoto(chatId, photoUrl, {caption: 'Public photo'});
|
||||
} catch (e) {
|
||||
photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
||||
}
|
||||
photoMessage = await sendProductPhoto(chatId, product.photo_url, 'Public photo');
|
||||
}
|
||||
if (product.hidden_photo_url) {
|
||||
const hiddenPhotoUrl = product.hidden_photo_url.startsWith('http') ? product.hidden_photo_url : `${process.env.ADMIN_URL}${product.hidden_photo_url}`;
|
||||
try {
|
||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, hiddenPhotoUrl, {caption: 'Hidden photo'});
|
||||
} catch (e) {
|
||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
||||
}
|
||||
hiddenPhotoMessage = await sendProductPhoto(chatId, product.hidden_photo_url, 'Hidden photo');
|
||||
}
|
||||
|
||||
await userStates.set(chatId, {
|
||||
msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
|
||||
msgToDelete: [photoMessage?.message_id, hiddenPhotoMessage?.message_id].filter(Boolean)
|
||||
})
|
||||
|
||||
await bot.deleteMessage(chatId, messageId);
|
||||
@@ -89,4 +105,4 @@ export default class ViewHandler {
|
||||
await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,35 @@ import userStates from "../../context/userStates.js";
|
||||
import ProductService from "../../services/productService.js";
|
||||
import CategoryService from "../../services/categoryService.js";
|
||||
import UserService from "../../services/userService.js";
|
||||
import PurchaseService from "../../services/purchaseService.js";
|
||||
import PurchaseService from '../../services/purchaseService.js';
|
||||
import Validators from '../../utils/validators.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
|
||||
const FALLBACK_PHOTO = path.join(process.cwd(), 'corrupt-photo.jpg');
|
||||
|
||||
function resolvePhotoSource(photoUrl) {
|
||||
if (!photoUrl) return null;
|
||||
if (photoUrl.startsWith('http')) return photoUrl;
|
||||
const filePath = path.join(UPLOADS_DIR, photoUrl.replace(/^\/uploads\//, ''));
|
||||
if (fs.existsSync(filePath)) return filePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function sendProductPhoto(chatId, photoUrl, caption) {
|
||||
const source = resolvePhotoSource(photoUrl);
|
||||
if (!source) return null;
|
||||
try {
|
||||
return await bot.sendPhoto(chatId, source, { caption });
|
||||
} catch (e) {
|
||||
logger.warn({ err: e, photoUrl }, 'Failed to send product photo');
|
||||
if (fs.existsSync(FALLBACK_PHOTO)) {
|
||||
return await bot.sendPhoto(chatId, FALLBACK_PHOTO, { caption });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
import logger from '../../utils/logger.js';
|
||||
|
||||
export default class UserProductHandler {
|
||||
@@ -356,13 +383,7 @@ export default class UserProductHandler {
|
||||
// Отправляем фото, если оно существует
|
||||
let photoMessage;
|
||||
if (product.photo_url) {
|
||||
const photoUrl = product.photo_url.startsWith('http') ? product.photo_url : `${process.env.ADMIN_URL}${product.photo_url}`;
|
||||
try {
|
||||
photoMessage = await bot.sendPhoto(chatId, photoUrl, { caption: 'Public photo' });
|
||||
} catch (e) {
|
||||
logger.warn({ err: e }, 'Failed to send product photo');
|
||||
photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Public photo' });
|
||||
}
|
||||
photoMessage = await sendProductPhoto(chatId, product.photo_url, 'Public photo');
|
||||
}
|
||||
|
||||
const keyboard = {
|
||||
@@ -708,13 +729,7 @@ export default class UserProductHandler {
|
||||
// Отправляем Hidden Photo
|
||||
let hiddenPhotoMessage;
|
||||
if (product.hidden_photo_url) {
|
||||
const hiddenPhotoUrl = product.hidden_photo_url.startsWith('http') ? product.hidden_photo_url : `${process.env.ADMIN_URL}${product.hidden_photo_url}`;
|
||||
try {
|
||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, hiddenPhotoUrl, { caption: 'Hidden photo' });
|
||||
} catch (e) {
|
||||
logger.warn({ err: e }, 'Failed to send hidden photo');
|
||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Hidden photo' });
|
||||
}
|
||||
hiddenPhotoMessage = await sendProductPhoto(chatId, product.hidden_photo_url, 'Hidden photo');
|
||||
}
|
||||
|
||||
const message = `
|
||||
|
||||
@@ -3,15 +3,42 @@
|
||||
|
||||
import config from "../../config/config.js";
|
||||
import db from '../../config/database.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import bot from "../../context/bot.js";
|
||||
import logger from "../../utils/logger.js";
|
||||
import PurchaseService from "../../services/purchaseService.js";
|
||||
import ProductService from "../../services/productService.js";
|
||||
import UserService from "../../services/userService.js";
|
||||
import LocationService from "../../services/locationService.js";
|
||||
import ProductService from "../../services/productService.js";
|
||||
import CategoryService from "../../services/categoryService.js";
|
||||
import bot from "../../context/bot.js";
|
||||
import WalletService from "../../services/walletService.js";
|
||||
import userStates from "../../context/userStates.js";
|
||||
import Validators from '../../utils/validators.js';
|
||||
import logger from '../../utils/logger.js';
|
||||
|
||||
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
|
||||
const FALLBACK_PHOTO = path.join(process.cwd(), 'corrupt-photo.jpg');
|
||||
|
||||
function resolvePhotoSource(photoUrl) {
|
||||
if (!photoUrl) return null;
|
||||
if (photoUrl.startsWith('http')) return photoUrl;
|
||||
const filePath = path.join(UPLOADS_DIR, photoUrl.replace(/^\/uploads\//, ''));
|
||||
if (fs.existsSync(filePath)) return filePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function sendProductPhoto(chatId, photoUrl, caption) {
|
||||
const source = resolvePhotoSource(photoUrl);
|
||||
if (!source) return null;
|
||||
try {
|
||||
return await bot.sendPhoto(chatId, source, { caption });
|
||||
} catch (e) {
|
||||
if (fs.existsSync(FALLBACK_PHOTO)) {
|
||||
return await bot.sendPhoto(chatId, FALLBACK_PHOTO, { caption });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default class UserPurchaseHandler {
|
||||
static async viewPurchasePage(userId, page) {
|
||||
@@ -180,12 +207,7 @@ export default class UserPurchaseHandler {
|
||||
// Отправляем Hidden Photo
|
||||
let hiddenPhotoMessage;
|
||||
if (product.hidden_photo_url) {
|
||||
const hiddenPhotoUrl = product.hidden_photo_url.startsWith('http') ? product.hidden_photo_url : `${process.env.ADMIN_URL}${product.hidden_photo_url}`;
|
||||
try {
|
||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, hiddenPhotoUrl, { caption: 'Hidden photo' });
|
||||
} catch (e) {
|
||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Hidden photo' });
|
||||
}
|
||||
hiddenPhotoMessage = await sendProductPhoto(chatId, product.hidden_photo_url, 'Hidden photo');
|
||||
}
|
||||
|
||||
// Формируем сообщение с деталями покупки
|
||||
|
||||
Reference in New Issue
Block a user