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 LocationService from '../../../services/locationService.js';
|
||||||
import ProductService from '../../../services/productService.js';
|
import ProductService from '../../../services/productService.js';
|
||||||
import logger from '../../../utils/logger.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 {
|
export default class ViewHandler {
|
||||||
|
|
||||||
@@ -62,24 +88,14 @@ export default class ViewHandler {
|
|||||||
let hiddenPhotoMessage;
|
let hiddenPhotoMessage;
|
||||||
|
|
||||||
if (product.photo_url) {
|
if (product.photo_url) {
|
||||||
const photoUrl = product.photo_url.startsWith('http') ? product.photo_url : `${process.env.ADMIN_URL}${product.photo_url}`;
|
photoMessage = await sendProductPhoto(chatId, product.photo_url, 'Public photo');
|
||||||
try {
|
|
||||||
photoMessage = await bot.sendPhoto(chatId, photoUrl, {caption: 'Public photo'});
|
|
||||||
} catch (e) {
|
|
||||||
photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (product.hidden_photo_url) {
|
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}`;
|
hiddenPhotoMessage = await sendProductPhoto(chatId, product.hidden_photo_url, 'Hidden photo');
|
||||||
try {
|
|
||||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, hiddenPhotoUrl, {caption: 'Hidden photo'});
|
|
||||||
} catch (e) {
|
|
||||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await userStates.set(chatId, {
|
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);
|
await bot.deleteMessage(chatId, messageId);
|
||||||
@@ -89,4 +105,4 @@ export default class ViewHandler {
|
|||||||
await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
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 ProductService from "../../services/productService.js";
|
||||||
import CategoryService from "../../services/categoryService.js";
|
import CategoryService from "../../services/categoryService.js";
|
||||||
import UserService from "../../services/userService.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 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';
|
import logger from '../../utils/logger.js';
|
||||||
|
|
||||||
export default class UserProductHandler {
|
export default class UserProductHandler {
|
||||||
@@ -356,13 +383,7 @@ export default class UserProductHandler {
|
|||||||
// Отправляем фото, если оно существует
|
// Отправляем фото, если оно существует
|
||||||
let photoMessage;
|
let photoMessage;
|
||||||
if (product.photo_url) {
|
if (product.photo_url) {
|
||||||
const photoUrl = product.photo_url.startsWith('http') ? product.photo_url : `${process.env.ADMIN_URL}${product.photo_url}`;
|
photoMessage = await sendProductPhoto(chatId, product.photo_url, 'Public photo');
|
||||||
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' });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
@@ -708,13 +729,7 @@ export default class UserProductHandler {
|
|||||||
// Отправляем Hidden Photo
|
// Отправляем Hidden Photo
|
||||||
let hiddenPhotoMessage;
|
let hiddenPhotoMessage;
|
||||||
if (product.hidden_photo_url) {
|
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}`;
|
hiddenPhotoMessage = await sendProductPhoto(chatId, product.hidden_photo_url, 'Hidden photo');
|
||||||
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' });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = `
|
const message = `
|
||||||
|
|||||||
@@ -3,15 +3,42 @@
|
|||||||
|
|
||||||
import config from "../../config/config.js";
|
import config from "../../config/config.js";
|
||||||
import db from '../../config/database.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 PurchaseService from "../../services/purchaseService.js";
|
||||||
|
import ProductService from "../../services/productService.js";
|
||||||
import UserService from "../../services/userService.js";
|
import UserService from "../../services/userService.js";
|
||||||
import LocationService from "../../services/locationService.js";
|
import LocationService from "../../services/locationService.js";
|
||||||
import ProductService from "../../services/productService.js";
|
|
||||||
import CategoryService from "../../services/categoryService.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 userStates from "../../context/userStates.js";
|
||||||
import Validators from '../../utils/validators.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 {
|
export default class UserPurchaseHandler {
|
||||||
static async viewPurchasePage(userId, page) {
|
static async viewPurchasePage(userId, page) {
|
||||||
@@ -180,12 +207,7 @@ export default class UserPurchaseHandler {
|
|||||||
// Отправляем Hidden Photo
|
// Отправляем Hidden Photo
|
||||||
let hiddenPhotoMessage;
|
let hiddenPhotoMessage;
|
||||||
if (product.hidden_photo_url) {
|
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}`;
|
hiddenPhotoMessage = await sendProductPhoto(chatId, product.hidden_photo_url, 'Hidden photo');
|
||||||
try {
|
|
||||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, hiddenPhotoUrl, { caption: 'Hidden photo' });
|
|
||||||
} catch (e) {
|
|
||||||
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Hidden photo' });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формируем сообщение с деталями покупки
|
// Формируем сообщение с деталями покупки
|
||||||
|
|||||||
Reference in New Issue
Block a user