other
This commit is contained in:
44
backend/utils/createAdmin.js
Normal file
44
backend/utils/createAdmin.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import mongoose from "mongoose";
|
||||
import bcrypt from "bcryptjs";
|
||||
import dotenv from "dotenv";
|
||||
import Staff from "../models/Staff.model.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
mongoose
|
||||
.connect(process.env.MONGO_URI, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
})
|
||||
.then(() => console.log("✅ MongoDB Connected"))
|
||||
.catch((err) => console.error("❌ MongoDB Connection Error:", err));
|
||||
|
||||
const createAdmin = async () => {
|
||||
const username = "admin";
|
||||
const password = "88sO)£2igu-:";
|
||||
|
||||
try {
|
||||
const existingAdmin = await Staff.findOne({ username });
|
||||
if (existingAdmin) {
|
||||
console.log("⚠️ Admin user already exists.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const admin = new Staff({
|
||||
username,
|
||||
passwordHash: hashedPassword,
|
||||
role: "admin",
|
||||
});
|
||||
|
||||
await admin.save();
|
||||
console.log(`✅ Admin user '${username}' created successfully!`);
|
||||
process.exit();
|
||||
} catch (error) {
|
||||
console.error("❌ Error creating admin user:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
createAdmin();
|
||||
56
backend/utils/createFakeOrder.js
Normal file
56
backend/utils/createFakeOrder.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import mongoose from "mongoose";
|
||||
import dotenv from "dotenv";
|
||||
import Order from "../models/Order.model.js"; // Adjust path if needed
|
||||
import Vendor from "../models/Vendor.model.js"; // Import Vendor model
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// ✅ Connect to MongoDB
|
||||
const mongoUri = process.env.MONGO_URI || "mongodb://localhost:27017/yourDatabaseName";
|
||||
mongoose
|
||||
.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then(() => console.log("✅ Connected to MongoDB"))
|
||||
.catch((err) => console.error("❌ MongoDB Connection Error:", err));
|
||||
|
||||
// ✅ Insert Fake Order for an Existing Vendor
|
||||
async function insertFakeOrder() {
|
||||
try {
|
||||
// ✅ Find an existing vendor
|
||||
const existingVendor = await Vendor.findOne();
|
||||
if (!existingVendor) {
|
||||
console.log("❌ No vendors found. Create a vendor first.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Using Vendor: ${existingVendor.username} (${existingVendor._id})`);
|
||||
|
||||
const fakeOrder = new Order({
|
||||
buyerId: new mongoose.Types.ObjectId(), // Fake buyer
|
||||
vendorId: existingVendor._id, // Assign to existing vendor
|
||||
storeId: new mongoose.Types.ObjectId(),
|
||||
products: [
|
||||
{
|
||||
productId: new mongoose.Types.ObjectId(),
|
||||
quantity: 2,
|
||||
pricePerUnit: 25.99,
|
||||
totalItemPrice: 51.98,
|
||||
},
|
||||
],
|
||||
totalPrice: 51.98,
|
||||
status: "paid",
|
||||
paymentAddress: "ltc1qxyzfakeaddress123456",
|
||||
txid: "faketxid1234567890abcdef",
|
||||
escrowExpiresAt: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000), // 8 days from now
|
||||
});
|
||||
|
||||
const savedOrder = await fakeOrder.save();
|
||||
console.log("✅ Fake Order Inserted:", savedOrder);
|
||||
} catch (error) {
|
||||
console.error("❌ Error inserting fake order:", error);
|
||||
} finally {
|
||||
mongoose.connection.close(); // Close DB connection
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Run Script
|
||||
insertFakeOrder();
|
||||
67
backend/utils/createInvitation.js
Normal file
67
backend/utils/createInvitation.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import mongoose from "mongoose";
|
||||
import dotenv from "dotenv";
|
||||
import Staff from "../models/Staff.model.js"
|
||||
import Invitation from "../models/Invitation.model.js"
|
||||
import crypto from "crypto";
|
||||
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// ✅ Connect to MongoDB
|
||||
const mongoUri = process.env.MONGO_URI;
|
||||
mongoose
|
||||
.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then(() => console.log("✅ Connected to MongoDB"))
|
||||
.catch((err) => console.error("❌ MongoDB Connection Error:", err));
|
||||
|
||||
const generateInviteCode = () => {
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
};
|
||||
|
||||
const createInvitation = async (staffEmail) => {
|
||||
try {
|
||||
// Find staff member
|
||||
const staff = await Staff.findOne({ username: "admin" });
|
||||
if (!staff) {
|
||||
throw new Error("Staff member not found");
|
||||
}
|
||||
|
||||
// Check if staff has permission to create invitations
|
||||
if (!['admin', 'support'].includes(staff.role)) {
|
||||
throw new Error("Insufficient permissions to create vendor invitations");
|
||||
}
|
||||
|
||||
// Generate unique invite code
|
||||
const inviteCode = generateInviteCode();
|
||||
|
||||
// Create invitation
|
||||
const invitation = await Invitation.create({
|
||||
code: inviteCode,
|
||||
createdBy: staff._id,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now
|
||||
isUsed: false
|
||||
});
|
||||
|
||||
console.log(`✅ Vendor invitation created successfully!
|
||||
Code: ${invitation.code}
|
||||
Created by: ${staff.email}
|
||||
Expires: ${invitation.expiresAt}
|
||||
`);
|
||||
|
||||
// Exit process after creating invitation
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("❌ Error creating invitation:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Get staff email from command line argument
|
||||
const staffEmail = process.argv[2];
|
||||
|
||||
if (!staffEmail) {
|
||||
console.error("❌ Please provide staff email: node createInvitation.js <staffEmail>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
createInvitation(staffEmail);
|
||||
7
backend/utils/createKeys.js
Normal file
7
backend/utils/createKeys.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
const encryptionKey = crypto.randomBytes(32).toString("hex"); // 32 bytes (256-bit)
|
||||
const iv = crypto.randomBytes(16).toString("hex"); // 16 bytes (128-bit)
|
||||
|
||||
console.log("Encryption Key:", encryptionKey);
|
||||
console.log("IV:", iv);
|
||||
55
backend/utils/disableStockTracking.js
Normal file
55
backend/utils/disableStockTracking.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
import Product from '../models/Product.model.js';
|
||||
import logger from './logger.js';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Connect to MongoDB
|
||||
mongoose.connect(process.env.MONGO_URI)
|
||||
.then(() => logger.info('MongoDB connected'))
|
||||
.catch(err => {
|
||||
logger.error('MongoDB connection error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
async function disableStockTrackingForAllProducts() {
|
||||
try {
|
||||
logger.info('Disabling stock tracking for all products...');
|
||||
|
||||
const result = await Product.updateMany(
|
||||
{}, // Empty filter matches all documents
|
||||
{
|
||||
stockTracking: false,
|
||||
stockStatus: "in_stock" // Set a default status
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Stock tracking disabled for ${result.modifiedCount} products out of ${result.matchedCount} total products`);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error('Error disabling stock tracking:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close database connection
|
||||
mongoose.connection.close();
|
||||
logger.info('Database connection closed');
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the function if this script is run directly
|
||||
if (process.argv[1].includes('disableStockTracking.js')) {
|
||||
disableStockTrackingForAllProducts()
|
||||
.then(() => {
|
||||
logger.info('Script completed successfully');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Script failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default disableStockTrackingForAllProducts;
|
||||
54
backend/utils/encryptPgp.js
Normal file
54
backend/utils/encryptPgp.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as openpgp from 'openpgp';
|
||||
import logger from './logger.js';
|
||||
|
||||
/**
|
||||
* Encrypts a message using PGP
|
||||
* @param {string} message - The message to encrypt
|
||||
* @param {string} publicKey - PGP public key for encryption
|
||||
* @returns {Promise<string>} - The encrypted message
|
||||
*/
|
||||
export const encryptWithPGP = async (message, publicKey) => {
|
||||
try {
|
||||
// Parse the public key
|
||||
const decodedPublicKey = await openpgp.readKey({ armoredKey: publicKey });
|
||||
|
||||
// Encrypt the message
|
||||
const encrypted = await openpgp.encrypt({
|
||||
message: await openpgp.createMessage({ text: message }),
|
||||
encryptionKeys: decodedPublicKey
|
||||
});
|
||||
|
||||
return encrypted;
|
||||
} catch (error) {
|
||||
logger.error('Error during PGP encryption', { error: error.message });
|
||||
throw new Error('Failed to encrypt message: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypts a message using PGP
|
||||
* @param {string} encryptedMessage - The encrypted message
|
||||
* @param {string} privateKey - PGP private key for decryption
|
||||
* @param {string} passphrase - Passphrase for the private key
|
||||
* @returns {Promise<string>} - The decrypted message
|
||||
*/
|
||||
export const decryptWithPGP = async (encryptedMessage, privateKey, passphrase) => {
|
||||
try {
|
||||
// Parse the private key
|
||||
const decodedPrivateKey = await openpgp.readPrivateKey({
|
||||
armoredKey: privateKey
|
||||
});
|
||||
|
||||
// Decrypt the message
|
||||
const decrypted = await openpgp.decrypt({
|
||||
message: await openpgp.readMessage({ armoredMessage: encryptedMessage }),
|
||||
decryptionKeys: decodedPrivateKey,
|
||||
config: { allowInsecureDecryptionWithSignature: true }
|
||||
});
|
||||
|
||||
return decrypted.data;
|
||||
} catch (error) {
|
||||
logger.error('Error during PGP decryption', { error: error.message });
|
||||
throw new Error('Failed to decrypt message: ' + error.message);
|
||||
}
|
||||
};
|
||||
96
backend/utils/litecoin/index.js
Normal file
96
backend/utils/litecoin/index.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import ky from "ky";
|
||||
|
||||
const rpcUrl = "http://152.53.124.126:9332/";
|
||||
const rpcUser = "notiiwasntherexdddd";
|
||||
const rpcPassword = "NYwsxePgMrThiapHnfCzUfaEfVlNKZECwvlqhHcWjerlZfcaTp";
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Basic ${Buffer.from(`${rpcUser}:${rpcPassword}`).toString(
|
||||
"base64"
|
||||
)}`,
|
||||
};
|
||||
|
||||
async function callRpc(method, params = [], walletName = null) {
|
||||
console.log(`Calling RPC method: ${method} with params: ${JSON.stringify(params)}`);
|
||||
const url = walletName ? `${rpcUrl}wallet/${walletName}` : rpcUrl;
|
||||
|
||||
try {
|
||||
const response = await ky
|
||||
.post(url, {
|
||||
json: {
|
||||
jsonrpc: "1.0",
|
||||
id: "curltest",
|
||||
method,
|
||||
params,
|
||||
},
|
||||
headers,
|
||||
})
|
||||
.json();
|
||||
|
||||
console.log(
|
||||
`RPC Response for method ${method}:`,
|
||||
JSON.stringify(response, null, 2)
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(`RPC Error: ${JSON.stringify(response.error, null, 2)}`);
|
||||
}
|
||||
|
||||
return response.result;
|
||||
} catch (error) {
|
||||
console.error(`Error calling RPC method ${method}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function createWallet(walletName) {
|
||||
const result = await callRpc("createwallet", [walletName]);
|
||||
console.log("Wallet created:", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function generateAddress(walletName = null) {
|
||||
const address = await callRpc("getnewaddress", [], walletName);
|
||||
console.log("Generated Address:", address);
|
||||
return address;
|
||||
}
|
||||
|
||||
async function checkWalletLoaded(walletName) {
|
||||
const walletInfo = await callRpc("getwalletinfo", [], walletName);
|
||||
console.log("Wallet Info:", walletInfo);
|
||||
return walletInfo;
|
||||
}
|
||||
|
||||
async function walletExists(walletName) {
|
||||
try {
|
||||
await callRpc("getwalletinfo", [], walletName);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function dumpPrivateKey(address, walletName) {
|
||||
const privateKey = await callRpc("dumpprivkey", [address], walletName);
|
||||
console.log("Private Key:", privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
async function setupWallet(walletName) {
|
||||
const createResult = await createWallet(walletName);
|
||||
const address = await generateAddress(walletName);
|
||||
|
||||
console.log("Address:", address);
|
||||
console.log("Wallet Name:", walletName);
|
||||
console.log("Create Result:", createResult);
|
||||
|
||||
const privKey = await dumpPrivateKey(address, walletName);
|
||||
|
||||
console.log("Wallet Info:", privKey);
|
||||
|
||||
|
||||
return { address, privKey };
|
||||
}
|
||||
|
||||
export { setupWallet };
|
||||
77
backend/utils/logger.js
Normal file
77
backend/utils/logger.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const colors = {
|
||||
reset: "\x1b[0m",
|
||||
bright: "\x1b[1m",
|
||||
dim: "\x1b[2m",
|
||||
underscore: "\x1b[4m",
|
||||
blink: "\x1b[5m",
|
||||
reverse: "\x1b[7m",
|
||||
hidden: "\x1b[8m",
|
||||
|
||||
fgBlack: "\x1b[30m",
|
||||
fgRed: "\x1b[31m",
|
||||
fgGreen: "\x1b[32m",
|
||||
fgYellow: "\x1b[33m",
|
||||
fgBlue: "\x1b[34m",
|
||||
fgMagenta: "\x1b[35m",
|
||||
fgCyan: "\x1b[36m",
|
||||
fgWhite: "\x1b[37m",
|
||||
|
||||
bgBlack: "\x1b[40m",
|
||||
bgRed: "\x1b[41m",
|
||||
bgGreen: "\x1b[42m",
|
||||
bgYellow: "\x1b[43m",
|
||||
bgBlue: "\x1b[44m",
|
||||
bgMagenta: "\x1b[45m",
|
||||
bgCyan: "\x1b[46m",
|
||||
bgWhite: "\x1b[47m",
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a timestamp for logging.
|
||||
* @returns {string} Formatted timestamp
|
||||
*/
|
||||
const getTimestamp = () => {
|
||||
return new Date().toISOString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs an INFO message.
|
||||
* @param {string} message - Log message
|
||||
* @param {Object} [data] - Additional data
|
||||
*/
|
||||
const info = (message, data = null) => {
|
||||
console.log(`${colors.fgGreen}[INFO] ${getTimestamp()} - ${message}${colors.reset}`);
|
||||
if (data) console.log(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a WARNING message.
|
||||
* @param {string} message - Log message
|
||||
* @param {Object} [data] - Additional data
|
||||
*/
|
||||
const warn = (message, data = null) => {
|
||||
console.warn(`${colors.fgYellow}[WARN] ${getTimestamp()} - ${message}${colors.reset}`);
|
||||
if (data) console.warn(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs an ERROR message.
|
||||
* @param {string} message - Log message
|
||||
* @param {Object} [data] - Additional data
|
||||
*/
|
||||
const error = (message, data = null) => {
|
||||
console.error(`${colors.fgRed}[ERROR] ${getTimestamp()} - ${message}${colors.reset}`);
|
||||
if (data) console.error(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a DEBUG message.
|
||||
* @param {string} message - Log message
|
||||
* @param {Object} [data] - Additional data
|
||||
*/
|
||||
const debug = (message, data = null) => {
|
||||
console.log(`${colors.fgBlue}[DEBUG] ${getTimestamp()} - ${message}${colors.reset}`);
|
||||
if (data) console.log(data);
|
||||
};
|
||||
|
||||
export default { info, warn, error, debug };
|
||||
171
backend/utils/telegramUtils.js
Normal file
171
backend/utils/telegramUtils.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import ky from "ky";
|
||||
import logger from "../utils/logger.js";
|
||||
import FormData from "form-data";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// Function to escape special characters for MarkdownV2
|
||||
const escapeMarkdown = (text) => {
|
||||
return text.replace(/([_*\[\]()~`>#+=|{}.!\\-])/g, '\\$1');
|
||||
};
|
||||
|
||||
// Function to preserve markdown formatting while escaping other special characters
|
||||
const preserveMarkdown = (text) => {
|
||||
// First, temporarily replace valid markdown
|
||||
text = text
|
||||
.replace(/\*\*(.*?)\*\*/g, '%%%BOLD%%%$1%%%BOLD%%%')
|
||||
.replace(/__(.*?)__/g, '%%%ITALIC%%%$1%%%ITALIC%%%')
|
||||
.replace(/`(.*?)`/g, '%%%CODE%%%$1%%%CODE%%%')
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, '%%%LINK_TEXT%%%$1%%%LINK_URL%%%$2%%%LINK%%%');
|
||||
|
||||
// Escape all special characters
|
||||
text = escapeMarkdown(text);
|
||||
|
||||
// Restore markdown formatting
|
||||
return text
|
||||
.replace(/%%%BOLD%%%(.*?)%%%BOLD%%%/g, '*$1*')
|
||||
.replace(/%%%ITALIC%%%(.*?)%%%ITALIC%%%/g, '_$1_')
|
||||
.replace(/%%%CODE%%%(.*?)%%%CODE%%%/g, '`$1`')
|
||||
.replace(/%%%LINK_TEXT%%%(.*?)%%%LINK_URL%%%(.*?)%%%LINK%%%/g, '[$1]($2)');
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message via a Telegram bot.
|
||||
* @param {string} telegramToken - The bot's API token.
|
||||
* @param {number} chatId - The recipient's Telegram chat ID.
|
||||
* @param {string} message - The message text.
|
||||
* @param {string} [photoPath] - Optional path to photo file.
|
||||
* @returns {Promise<boolean>} - Returns `true` if the message is sent successfully, otherwise `false`.
|
||||
*/
|
||||
export const sendTelegramMessage = async (telegramToken, chatId, message, photoPath = null) => {
|
||||
try {
|
||||
// Process message text if it exists
|
||||
const processedMessage = message?.trim() ? preserveMarkdown(message) : '';
|
||||
|
||||
if (photoPath) {
|
||||
const formData = new FormData();
|
||||
formData.append('chat_id', chatId);
|
||||
|
||||
// Debug information before reading file
|
||||
logger.info('Attempting to read file:', {
|
||||
photoPath,
|
||||
exists: fs.existsSync(photoPath),
|
||||
stats: fs.existsSync(photoPath) ? fs.statSync(photoPath) : null,
|
||||
dirname: path.dirname(photoPath),
|
||||
basename: path.basename(photoPath)
|
||||
});
|
||||
|
||||
try {
|
||||
// Ensure the file exists before reading
|
||||
if (!fs.existsSync(photoPath)) {
|
||||
throw new Error(`File does not exist at path: ${photoPath}`);
|
||||
}
|
||||
|
||||
// Create read stream and append to FormData
|
||||
const photoStream = fs.createReadStream(photoPath);
|
||||
formData.append('photo', photoStream);
|
||||
|
||||
if (processedMessage) {
|
||||
formData.append('caption', processedMessage);
|
||||
formData.append('parse_mode', 'MarkdownV2');
|
||||
}
|
||||
|
||||
// Debug the FormData contents
|
||||
logger.info('FormData created:', {
|
||||
boundaryUsed: formData.getBoundary?.() || 'No boundary found',
|
||||
headers: formData.getHeaders?.() || 'No headers available',
|
||||
messageLength: processedMessage.length
|
||||
});
|
||||
|
||||
// Wait for the entire request to complete
|
||||
await new Promise((resolve, reject) => {
|
||||
formData.submit({
|
||||
host: 'api.telegram.org',
|
||||
path: `/bot${telegramToken}/sendPhoto`,
|
||||
protocol: 'https:',
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
let data = '';
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const response = JSON.parse(data);
|
||||
if (!response.ok) {
|
||||
reject(new Error(response.description || 'Failed to send photo'));
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(new Error('Failed to parse Telegram response'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// Log detailed error information
|
||||
logger.error(`❌ Telegram API Error for chat ${chatId}:`, {
|
||||
error: error.message,
|
||||
response: error.response ? await error.response.json().catch(() => ({})) : undefined,
|
||||
photoPath,
|
||||
messageLength: processedMessage.length,
|
||||
fileExists: fs.existsSync(photoPath),
|
||||
fileStats: fs.existsSync(photoPath) ? fs.statSync(photoPath) : null
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await ky.post(`https://api.telegram.org/bot${telegramToken}/sendMessage`, {
|
||||
json: {
|
||||
chat_id: chatId,
|
||||
text: processedMessage,
|
||||
parse_mode: "MarkdownV2",
|
||||
},
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (error) {
|
||||
// Log detailed error information
|
||||
logger.error(`❌ Telegram API Error for chat ${chatId}:`, {
|
||||
error: error.message,
|
||||
response: await error.response?.json().catch(() => ({})),
|
||||
messageLength: processedMessage.length
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`✅ Telegram message sent to ${chatId}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to send Telegram message to ${chatId}: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends messages in bulk while respecting Telegram's rate limits.
|
||||
* @param {string} telegramToken - The bot's API token.
|
||||
* @param {Array<number>} chatIds - An array of chat IDs to send to.
|
||||
* @param {string} message - The message text.
|
||||
* @param {string} [photoPath] - Optional path to photo file.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const sendBulkTelegramMessages = async (telegramToken, chatIds, message, photoPath = null) => {
|
||||
for (let i = 0; i < chatIds.length; i++) {
|
||||
const chatId = chatIds[i];
|
||||
await sendTelegramMessage(telegramToken, chatId, message, photoPath);
|
||||
|
||||
if ((i + 1) % 30 === 0) {
|
||||
logger.info(`⏳ Rate limit pause (sent ${i + 1} messages)...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
};
|
||||
0
backend/utils/walletUtils.js
Normal file
0
backend/utils/walletUtils.js
Normal file
Reference in New Issue
Block a user