Configure BTCPay with external nodes via Tor

- Set up Tor container for SOCKS proxy (port 9050)
- Configured Monero wallet with remote onion node
- Bitcoin node continues syncing in background (60% complete)
- Created documentation for wallet configuration steps
- All external connections routed through Tor for privacy

BTCPay requires manual wallet configuration through web interface:
- Bitcoin: Need to add xpub/zpub for watch-only wallet
- Monero: Need to add address and view key

System ready for payment acceptance once wallets configured.
This commit is contained in:
SilverLabs DevTeam
2025-09-19 12:14:39 +01:00
parent 36b393dd2e
commit 73e8773ea3
195 changed files with 77086 additions and 198 deletions

131
TeleBot/.env.multi.example Normal file
View File

@@ -0,0 +1,131 @@
# LittleShop Multi-Bot Environment Configuration Template
# Copy this file to .env.multi and configure your values
# ========================================
# SHARED CONFIGURATION (All Bots)
# ========================================
# LittleShop API Configuration
LITTLESHOP_API_URL=http://localhost:8080
# For remote API: https://api.yourdomain.com
# For Docker host: http://host.docker.internal:8080
# For same network: http://littleshop-api:8080
LITTLESHOP_USERNAME=admin
LITTLESHOP_PASSWORD=admin
# Redis Configuration (Optional - for shared caching)
REDIS_ENABLED=false
REDIS_PASSWORD=your_secure_redis_password_here
# ========================================
# SUPPORT BOT CONFIGURATION
# ========================================
# Telegram Bot Token from @BotFather
SUPPORT_BOT_TOKEN=
# Admin Chat ID for notifications (get from @userinfobot)
SUPPORT_ADMIN_CHAT_ID=
# Bot API Key from LittleShop Admin Panel
SUPPORT_BOT_API_KEY=
# 32-character encryption key for database
SUPPORT_DB_ENCRYPTION_KEY=change_this_to_32_char_secure_key
# ========================================
# SALES & MARKETING BOT CONFIGURATION
# ========================================
# Telegram Bot Token from @BotFather
SALES_BOT_TOKEN=
# Admin Chat ID for notifications
SALES_ADMIN_CHAT_ID=
# Bot API Key from LittleShop Admin Panel
SALES_BOT_API_KEY=
# 32-character encryption key for database
SALES_DB_ENCRYPTION_KEY=change_this_to_32_char_secure_key
# ========================================
# VIP/PREMIUM BOT CONFIGURATION
# ========================================
# Telegram Bot Token from @BotFather
VIP_BOT_TOKEN=
# Admin Chat ID for notifications
VIP_ADMIN_CHAT_ID=
# Bot API Key from LittleShop Admin Panel
VIP_BOT_API_KEY=
# 32-character encryption key for database
VIP_DB_ENCRYPTION_KEY=change_this_to_32_char_secure_key
# Enhanced privacy for VIP customers
VIP_ENABLE_TOR=false
# ========================================
# EU REGION BOT CONFIGURATION (Optional)
# ========================================
# Telegram Bot Token from @BotFather
EU_BOT_TOKEN=
# Admin Chat ID for notifications
EU_ADMIN_CHAT_ID=
# Bot API Key from LittleShop Admin Panel
EU_BOT_API_KEY=
# 32-character encryption key for database
EU_DB_ENCRYPTION_KEY=change_this_to_32_char_secure_key
# Optional: Different API endpoint for EU region
EU_API_URL=${LITTLESHOP_API_URL}
# ========================================
# DEPLOYMENT NOTES
# ========================================
# To generate secure encryption keys:
# openssl rand -hex 16 # Generates 32-character hex string
# or
# cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1
# To get your Telegram Chat ID:
# 1. Message @userinfobot on Telegram
# 2. It will reply with your user info including chat ID
# To create bot tokens:
# 1. Message @BotFather on Telegram
# 2. Send /newbot and follow instructions
# 3. Copy the token provided
# To get Bot API Keys:
# 1. Login to LittleShop Admin Panel
# 2. Go to /Admin/Bots/Wizard
# 3. Create bot and copy the API key
# ========================================
# DOCKER DEPLOYMENT COMMANDS
# ========================================
# Deploy all bots:
# docker-compose -f docker-compose.multi.yml --env-file .env.multi up -d
# Deploy specific bot:
# docker-compose -f docker-compose.multi.yml --env-file .env.multi up -d bot-support
# View logs:
# docker-compose -f docker-compose.multi.yml logs -f bot-support
# Stop all bots:
# docker-compose -f docker-compose.multi.yml down
# Remove all data (CAUTION):
# docker-compose -f docker-compose.multi.yml down -v

View File

@@ -0,0 +1,504 @@
# 🚀 Multi-Host Docker Bot Deployment Guide
Deploy multiple LittleShop TeleBots across different Docker hosts for scalability, redundancy, and geographic distribution.
## 📋 Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Deployment Methods](#deployment-methods)
- [Configuration](#configuration)
- [Management](#management)
- [Security](#security)
- [Troubleshooting](#troubleshooting)
## Overview
The LittleShop TeleBot system supports deploying multiple bot instances across different Docker hosts. Each bot:
- Runs independently in its own container
- Connects to the central LittleShop API
- Has its own Telegram bot token and personality
- Can serve different customer segments or regions
## Architecture
```
┌─────────────────────────────────────────────────┐
│ Central LittleShop API │
│ (http://api.littleshop.com) │
└─────────────┬─────────┬─────────┬───────────────┘
│ │ │
┌────▼───┐ ┌──▼───┐ ┌──▼───┐
│Docker │ │Docker│ │Docker│
│Host A │ │Host B│ │Host C│
└────────┘ └──────┘ └──────┘
Support Sales VIP Bots
Bot Bot
```
## Quick Start
### 1. Build Docker Image
```bash
# Build the image locally
cd /silverlabs/src/LittleShop
docker build -f TeleBot/TeleBot/Dockerfile -t littleshop/telebot:latest .
# Push to registry (Docker Hub, GitLab, or private)
docker push littleshop/telebot:latest
```
### 2. Create Bot in Admin Panel
1. Navigate to `http://localhost:8080/Admin/Bots/Wizard`
2. Follow the wizard to create a bot with @BotFather
3. Save the bot token and API key
### 3. Deploy Bot
```bash
# Using the deployment script
./TeleBot/deploy-bot.sh \
-n my-bot \
-t "YOUR_BOT_TOKEN" \
-k "YOUR_API_KEY" \
-a "https://api.littleshop.com"
# Or using Docker directly
docker run -d \
--name littleshop-bot-sales \
-e Telegram__BotToken=YOUR_TOKEN \
-e LittleShop__ApiUrl=https://api.shop.com \
littleshop/telebot:latest
```
## Deployment Methods
### Method 1: Deployment Script
The `deploy-bot.sh` script provides an easy way to deploy bots:
```bash
# Deploy to local Docker
./deploy-bot.sh -n support-bot -t "TOKEN" -k "API_KEY"
# Deploy to remote host
./deploy-bot.sh -n sales-bot -t "TOKEN" -h ssh://user@server.com
# Deploy with all options
./deploy-bot.sh \
-n vip-bot \
-t "BOT_TOKEN" \
-k "API_KEY" \
-a https://api.shop.com \
-c "ADMIN_CHAT_ID" \
-e "32_CHAR_ENCRYPTION_KEY" \
-p "Sarah" \
-m strict \
--pull --rm
```
### Method 2: Docker Compose (Multiple Bots)
Deploy multiple bots on the same host:
```bash
# Copy and configure environment file
cp .env.multi.example .env.multi
# Edit .env.multi with your values
# Deploy all bots
docker-compose -f docker-compose.multi.yml --env-file .env.multi up -d
# Deploy specific bot
docker-compose -f docker-compose.multi.yml --env-file .env.multi up -d bot-support
# View logs
docker-compose -f docker-compose.multi.yml logs -f
```
### Method 3: Portainer Stack
1. **Add Template to Portainer**:
- Go to **App Templates****Custom Templates**
- Add the content from `portainer-template.json`
2. **Deploy from Template**:
- Go to **App Templates**
- Select "LittleShop TeleBot"
- Fill in environment variables
- Deploy
3. **Or Deploy Stack Directly**:
```bash
curl -X POST \
http://portainer:9000/api/stacks \
-H "X-API-Key: YOUR_PORTAINER_KEY" \
-F "Name=littleshop-bot" \
-F "StackFileContent=@docker-compose.yml" \
-F "Env=@.env"
```
### Method 4: Docker Swarm
Deploy across a Docker Swarm cluster:
```bash
# Initialize swarm (if not already)
docker swarm init
# Create secrets
echo "YOUR_TOKEN" | docker secret create bot_token -
echo "YOUR_API_KEY" | docker secret create bot_api_key -
# Deploy service
docker service create \
--name littleshop-bot \
--secret bot_token \
--secret bot_api_key \
--replicas 3 \
--env Telegram__BotToken_FILE=/run/secrets/bot_token \
--env BotManager__ApiKey_FILE=/run/secrets/bot_api_key \
littleshop/telebot:latest
```
## Configuration
### Environment Variables
| Variable | Description | Required |
|----------|-------------|----------|
| `Telegram__BotToken` | Bot token from @BotFather | ✅ |
| `LittleShop__ApiUrl` | LittleShop API endpoint | ✅ |
| `BotManager__ApiKey` | API key from admin panel | ⭕ |
| `Telegram__AdminChatId` | Admin notifications chat | ⭕ |
| `Database__EncryptionKey` | 32-char encryption key | ⭕ |
| `Privacy__Mode` | strict/moderate/relaxed | ⭕ |
### Bot Personalities
Configure different personalities for different bots:
```yaml
# Support Bot - Helpful and professional
Bot__PersonalityName: "Alan"
Bot__Tone: "professional"
# Sales Bot - Enthusiastic and engaging
Bot__PersonalityName: "Sarah"
Bot__Tone: "friendly"
# VIP Bot - Discrete and sophisticated
Bot__PersonalityName: "Emma"
Bot__Tone: "formal"
```
### Network Configuration
#### Same Network as API
```yaml
LittleShop__ApiUrl: http://littleshop-api:8080
```
#### Docker Host Network
```yaml
LittleShop__ApiUrl: http://host.docker.internal:8080
```
#### External API
```yaml
LittleShop__ApiUrl: https://api.littleshop.com
```
## Management
### Monitor Bots
```bash
# List all bot containers
docker ps --filter "label=com.littleshop.bot=true"
# Check bot health
docker inspect littleshop-bot-support --format='{{.State.Health.Status}}'
# View logs
docker logs -f littleshop-bot-support --tail 100
# Get metrics
docker stats littleshop-bot-support
```
### Update Bots
```bash
# Pull latest image
docker pull littleshop/telebot:latest
# Recreate container with new image
docker-compose -f docker-compose.multi.yml pull
docker-compose -f docker-compose.multi.yml up -d
# Or using deployment script
./deploy-bot.sh -n my-bot -t "TOKEN" --pull --rm
```
### Backup & Restore
```bash
# Backup bot data
docker run --rm \
-v littleshop-bot-support-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/bot-backup.tar.gz /data
# Restore bot data
docker run --rm \
-v littleshop-bot-support-data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/bot-backup.tar.gz -C /
```
## Deployment Scenarios
### Scenario 1: Geographic Distribution
Deploy bots closer to users for better latency:
```bash
# EU Bot on European server
ssh eu-server "docker run -d --name bot-eu \
-e Telegram__BotToken=$EU_TOKEN \
-e LittleShop__ApiUrl=https://api.shop.com \
-e TZ=Europe/London \
littleshop/telebot:latest"
# US Bot on US server
ssh us-server "docker run -d --name bot-us \
-e Telegram__BotToken=$US_TOKEN \
-e LittleShop__ApiUrl=https://api.shop.com \
-e TZ=America/New_York \
littleshop/telebot:latest"
# Asia Bot on Singapore server
ssh asia-server "docker run -d --name bot-asia \
-e Telegram__BotToken=$ASIA_TOKEN \
-e LittleShop__ApiUrl=https://api.shop.com \
-e TZ=Asia/Singapore \
littleshop/telebot:latest"
```
### Scenario 2: Customer Segmentation
Different bots for different customer types:
```yaml
# docker-compose.segments.yml
services:
bot-public:
image: littleshop/telebot:latest
environment:
- Privacy__Mode=moderate
- Features__EnableAnalytics=true
bot-vip:
image: littleshop/telebot:latest
environment:
- Privacy__Mode=strict
- Privacy__EnableTor=true
- Features__EnableOrderMixing=true
bot-wholesale:
image: littleshop/telebot:latest
environment:
- Features__BulkOrdering=true
- Features__B2BPricing=true
```
### Scenario 3: A/B Testing
Deploy multiple versions for testing:
```bash
# Version A - Standard features
docker run -d --name bot-version-a \
-e EXPERIMENT_VERSION=A \
littleshop/telebot:latest
# Version B - New features
docker run -d --name bot-version-b \
-e EXPERIMENT_VERSION=B \
-e Features__NewCheckout=true \
littleshop/telebot:v2-beta
```
## Security
### Best Practices
1. **Use Docker Secrets** for sensitive data:
```bash
echo "TOKEN" | docker secret create bot_token -
docker service create --secret bot_token ...
```
2. **Network Isolation**:
```yaml
networks:
frontend:
driver: overlay
encrypted: true
backend:
driver: overlay
internal: true
```
3. **Resource Limits**:
```yaml
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
```
4. **Health Checks**:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
```
### TLS/SSL Configuration
For production deployments with TLS:
```bash
docker run -d \
-v /path/to/certs:/certs:ro \
-e ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/cert.pfx \
-e ASPNETCORE_Kestrel__Certificates__Default__Password=CERT_PASSWORD \
-e ASPNETCORE_URLS="https://+:443" \
littleshop/telebot:latest
```
## Troubleshooting
### Common Issues
#### Bot Won't Start
```bash
# Check logs
docker logs littleshop-bot --tail 50
# Common causes:
# - Invalid bot token
# - Can't reach API
# - Port already in use
```
#### Can't Connect to API
```bash
# Test connectivity from container
docker exec littleshop-bot curl http://api-url/health
# Check DNS resolution
docker exec littleshop-bot nslookup api.shop.com
```
#### High Memory Usage
```bash
# Check memory stats
docker stats littleshop-bot
# Limit memory
docker update --memory 512m littleshop-bot
```
### Debug Mode
Enable debug logging:
```bash
docker run -d \
-e Logging__LogLevel__Default=Debug \
-e Logging__LogLevel__TeleBot=Trace \
littleshop/telebot:latest
```
### Performance Monitoring
```bash
# CPU and Memory usage
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# Network I/O
docker exec littleshop-bot netstat -i
# Disk usage
docker system df -v
```
## Advanced Topics
### Custom Docker Networks
Create isolated networks for different bot groups:
```bash
# Create networks
docker network create bots-support --driver overlay
docker network create bots-sales --driver overlay
docker network create bots-vip --driver overlay --opt encrypted
# Deploy with specific network
docker run -d --network bots-vip ...
```
### Load Balancing
Use multiple bot instances with same token:
```bash
# Note: Requires webhook mode and load balancer
docker service create \
--name bot-pool \
--replicas 5 \
--publish 8443:8443 \
-e Telegram__UseWebhook=true \
littleshop/telebot:latest
```
### Monitoring with Prometheus
```yaml
# Add to docker-compose
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
ports:
- '9090:9090'
```
## Support
For issues or questions:
1. Check container logs: `docker logs <container-name>`
2. Verify environment variables
3. Test API connectivity
4. Review admin panel bot status
5. Check the [main deployment guide](DEPLOYMENT.md)
## Next Steps
1. **Set up monitoring** - Add Prometheus/Grafana
2. **Implement CI/CD** - Automate deployments
3. **Add backup strategy** - Regular data backups
4. **Scale horizontally** - Add more hosts as needed
5. **Implement geo-routing** - Route users to nearest bot

View File

@@ -0,0 +1,7 @@
namespace TeleBot
{
public static class BotConfig
{
public static string BrandName { get; set; } = "Little Shop";
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using TeleBot.Services;
namespace TeleBot.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class WebhookController : ControllerBase
{
private readonly ILogger<WebhookController> _logger;
private readonly TelegramBotService _telegramBotService;
private readonly BotManagerService _botManagerService;
private readonly IConfiguration _configuration;
public WebhookController(
ILogger<WebhookController> logger,
TelegramBotService telegramBotService,
BotManagerService botManagerService,
IConfiguration configuration)
{
_logger = logger;
_telegramBotService = telegramBotService;
_botManagerService = botManagerService;
_configuration = configuration;
}
/// <summary>
/// Webhook endpoint for configuration updates from admin panel
/// </summary>
[HttpPost("config-update")]
public async Task<IActionResult> ConfigurationUpdate([FromBody] ConfigUpdateDto dto)
{
try
{
// Verify the request is authorized (you might want to add API key validation)
var apiKey = Request.Headers["X-Webhook-Key"].ToString();
var expectedKey = _configuration["Webhook:Secret"];
if (!string.IsNullOrEmpty(expectedKey) && apiKey != expectedKey)
{
_logger.LogWarning("Unauthorized webhook request");
return Unauthorized();
}
_logger.LogInformation("Received configuration update via webhook");
// Handle different types of updates
if (dto.UpdateType == "bot_token" && !string.IsNullOrEmpty(dto.BotToken))
{
_logger.LogInformation("Updating bot token via webhook");
await _telegramBotService.UpdateBotTokenAsync(dto.BotToken);
return Ok(new { success = true, message = "Bot token updated successfully" });
}
else if (dto.UpdateType == "settings")
{
_logger.LogInformation("Triggering settings sync via webhook");
// Force a settings sync
var settings = await _botManagerService.GetSettingsAsync();
if (settings != null)
{
_logger.LogInformation("Settings synced successfully via webhook");
}
return Ok(new { success = true, message = "Settings synced successfully" });
}
return BadRequest(new { success = false, message = "Unknown update type" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing webhook update");
return StatusCode(500, new { success = false, message = "Internal server error" });
}
}
/// <summary>
/// Health check endpoint
/// </summary>
[HttpGet("health")]
public IActionResult Health()
{
return Ok(new
{
status = "healthy",
timestamp = DateTime.UtcNow,
botKey = !string.IsNullOrEmpty(_configuration["BotManager:ApiKey"])
});
}
}
public class ConfigUpdateDto
{
public string UpdateType { get; set; } = string.Empty;
public string? BotToken { get; set; }
public Dictionary<string, object>? Settings { get; set; }
}
}

View File

@@ -11,6 +11,7 @@ using Telegram.Bot.Types.ReplyMarkups;
using TeleBot.Models;
using TeleBot.Services;
using TeleBot.UI;
using LittleShop.Client.Models;
namespace TeleBot.Handlers
{
@@ -97,7 +98,15 @@ namespace TeleBot.Handlers
case "add":
await HandleAddToCart(bot, callbackQuery, session, data);
break;
case "quickbuy":
await HandleQuickBuy(bot, callbackQuery, session, data);
break;
case "quickbuyvar":
await HandleQuickBuyWithVariation(bot, callbackQuery, session, data);
break;
case "cart":
await HandleViewCart(bot, callbackQuery.Message, session);
break;
@@ -281,7 +290,7 @@ namespace TeleBot.Handlers
// Use carousel service to send products with images
await _carouselService.SendProductCarouselAsync(bot, message.Chat.Id, products, categoryName, page);
session.State = SessionState.BrowsingProducts;
session.State = SessionState.ViewingProducts;
}
private async Task HandleProductsPage(ITelegramBotClient bot, Message message, UserSession session, string[] data)
@@ -294,7 +303,7 @@ namespace TeleBot.Handlers
// Use carousel service to send products with images
await _carouselService.SendProductCarouselAsync(bot, message.Chat.Id, products, "All Categories", page);
session.State = SessionState.BrowsingProducts;
session.State = SessionState.ViewingProducts;
}
private async Task HandleProductDetail(ITelegramBotClient bot, Message message, UserSession session, Guid productId)
@@ -336,27 +345,49 @@ namespace TeleBot.Handlers
private async Task HandleAddToCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data)
{
// Format: add:productId:quantity
// Format: add:productId:quantity or add:productId:quantity:variationId
var productId = Guid.Parse(data[1]);
var quantity = int.Parse(data[2]);
Guid? variationId = data.Length > 3 ? Guid.Parse(data[3]) : null;
var product = await _shopService.GetProductAsync(productId);
if (product == null)
{
await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true);
return;
}
session.Cart.AddItem(productId, product.Name, product.Price, quantity);
// If variations exist but none selected, show variation selection
if (variationId == null && product.Variations?.Any() == true)
{
await ShowVariationSelection(bot, callbackQuery.Message!, session, product, quantity);
return;
}
// Get price based on variation or base product
decimal price = product.Price;
string itemName = product.Name;
if (variationId.HasValue && product.Variations != null)
{
var variation = product.Variations.FirstOrDefault(v => v.Id == variationId);
if (variation != null)
{
price = variation.Price;
itemName = $"{product.Name} ({variation.Name})";
quantity = variation.Quantity; // Use variation's quantity
}
}
session.Cart.AddItem(productId, itemName, price, quantity, variationId);
await bot.AnswerCallbackQueryAsync(
callbackQuery.Id,
$"✅ Added {quantity}x {product.Name} to cart",
$"✅ Added {quantity}x {itemName} to cart",
showAlert: false
);
// Show cart
await HandleViewCart(bot, callbackQuery.Message!, session);
// Send new cart message instead of editing
await SendNewCartMessage(bot, callbackQuery.Message!.Chat.Id, session);
}
private async Task HandleViewCart(ITelegramBotClient bot, Message message, UserSession session)
@@ -370,6 +401,149 @@ namespace TeleBot.Handlers
);
session.State = SessionState.ViewingCart;
}
private async Task SendNewCartMessage(ITelegramBotClient bot, long chatId, UserSession session)
{
await bot.SendTextMessageAsync(
chatId,
MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart)
);
session.State = SessionState.ViewingCart;
}
private async Task ShowVariationSelection(ITelegramBotClient bot, Message message, UserSession session, Product product, int defaultQuantity)
{
var text = MessageFormatter.FormatProductWithVariations(product);
await bot.SendTextMessageAsync(
message.Chat.Id,
text,
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.ProductVariationsMenu(product, defaultQuantity)
);
}
private async Task HandleQuickBuy(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data)
{
// Format: quickbuy:productId:quantity
var productId = Guid.Parse(data[1]);
var quantity = int.Parse(data[2]);
var product = await _shopService.GetProductAsync(productId);
if (product == null)
{
await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true);
return;
}
// If variations exist, show variation selection with quickbuy flow
if (product.Variations?.Any() == true)
{
await ShowVariationSelectionForQuickBuy(bot, callbackQuery.Message!, session, product);
return;
}
// Add to cart with base product
session.Cart.AddItem(productId, product.Name, product.Price, quantity, null);
await bot.AnswerCallbackQueryAsync(
callbackQuery.Id,
$"✅ Added {quantity}x {product.Name} to cart",
showAlert: false
);
// Send cart summary in new message
await bot.SendTextMessageAsync(
callbackQuery.Message!.Chat.Id,
MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart)
);
// Immediately proceed to checkout
await Task.Delay(500); // Small delay for better UX
await HandleCheckout(bot, callbackQuery.Message, session);
}
private async Task ShowVariationSelectionForQuickBuy(ITelegramBotClient bot, Message message, UserSession session, Product product)
{
var text = MessageFormatter.FormatProductWithVariations(product);
var buttons = new List<InlineKeyboardButton[]>();
if (product.Variations?.Any() == true)
{
// Add buttons for each variation with quickbuy flow
foreach (var variation in product.Variations.OrderBy(v => v.Quantity))
{
var label = variation.Quantity > 1
? $"{variation.Name} - ${variation.Price:F2} (${variation.PricePerUnit:F2}/ea)"
: $"{variation.Name} - ${variation.Price:F2}";
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData(label, $"quickbuyvar:{product.Id}:{variation.Quantity}:{variation.Id}")
});
}
}
// Add back button
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData("⬅️ Back", "menu")
});
await bot.SendTextMessageAsync(
message.Chat.Id,
text + "\n\n*Select an option for quick checkout:*",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: new InlineKeyboardMarkup(buttons)
);
}
private async Task HandleQuickBuyWithVariation(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data)
{
// Format: quickbuyvar:productId:quantity:variationId
var productId = Guid.Parse(data[1]);
var quantity = int.Parse(data[2]);
var variationId = Guid.Parse(data[3]);
var product = await _shopService.GetProductAsync(productId);
if (product == null)
{
await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true);
return;
}
var variation = product.Variations?.FirstOrDefault(v => v.Id == variationId);
if (variation == null)
{
await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Variation not found", showAlert: true);
return;
}
// Add to cart with variation
var itemName = $"{product.Name} ({variation.Name})";
session.Cart.AddItem(productId, itemName, variation.Price, variation.Quantity, variationId);
await bot.AnswerCallbackQueryAsync(
callbackQuery.Id,
$"✅ Added {variation.Quantity}x {itemName} to cart",
showAlert: false
);
// Send cart summary in new message
await bot.SendTextMessageAsync(
callbackQuery.Message!.Chat.Id,
MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart)
);
// Immediately proceed to checkout
await Task.Delay(500); // Small delay for better UX
await HandleCheckout(bot, callbackQuery.Message, session);
}
private async Task HandleRemoveFromCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, Guid productId)
{
@@ -406,18 +580,18 @@ namespace TeleBot.Handlers
await bot.AnswerCallbackQueryAsync("", "Your cart is empty", showAlert: true);
return;
}
// Initialize order flow
session.OrderFlow = new OrderFlowData
{
UsePGPEncryption = session.Privacy.RequirePGP
};
session.State = SessionState.CheckoutFlow;
await bot.EditMessageTextAsync(
// Send new message for checkout instead of editing
await bot.SendTextMessageAsync(
message.Chat.Id,
message.MessageId,
"📦 *Checkout - Step 1/5*\n\n" +
"Please enter your shipping name:\n\n" +
"_Reply to this message with your name_",
@@ -459,9 +633,9 @@ namespace TeleBot.Handlers
// Store order ID for payment
session.TempData["current_order_id"] = order.Id;
// Show payment options
var currencies = _configuration.GetSection("Cryptocurrencies").Get<List<string>>()
?? new List<string> { "BTC", "XMR", "USDT", "LTC" };
// Show payment options - only safe currencies with BTCPay Server support
var currencies = _configuration.GetSection("Cryptocurrencies").Get<List<string>>()
?? new List<string> { "BTC", "XMR", "LTC", "DASH" };
await bot.EditMessageTextAsync(
message.Chat.Id,

View File

@@ -189,7 +189,7 @@ namespace TeleBot.Handlers
// Send products as carousel with images
await _carouselService.SendProductCarouselAsync(bot, message.Chat.Id, products, categoryName, 1);
session.State = Models.SessionState.BrowsingProducts;
session.State = Models.SessionState.ViewingProducts;
}
catch (Exception ex)
{

View File

@@ -11,10 +11,11 @@ namespace TeleBot.Models
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public void AddItem(Guid productId, string productName, decimal price, int quantity = 1)
public void AddItem(Guid productId, string productName, decimal price, int quantity = 1, Guid? variationId = null)
{
var existingItem = Items.FirstOrDefault(i => i.ProductId == productId);
var existingItem = Items.FirstOrDefault(i =>
i.ProductId == productId && i.VariationId == variationId);
if (existingItem != null)
{
existingItem.Quantity += quantity;
@@ -25,6 +26,7 @@ namespace TeleBot.Models
var newItem = new CartItem
{
ProductId = productId,
VariationId = variationId,
ProductName = productName,
UnitPrice = price,
Quantity = quantity
@@ -32,7 +34,7 @@ namespace TeleBot.Models
newItem.UpdateTotalPrice(); // Ensure total is calculated after all properties are set
Items.Add(newItem);
}
UpdatedAt = DateTime.UtcNow;
}
@@ -85,6 +87,7 @@ namespace TeleBot.Models
public class CartItem
{
public Guid ProductId { get; set; }
public Guid? VariationId { get; set; }
public string ProductName { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }

View File

@@ -4,6 +4,8 @@ using System.Threading.Tasks;
using Hangfire;
using Hangfire.LiteDB;
using LittleShop.Client.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -14,8 +16,7 @@ using TeleBot;
using TeleBot.Handlers;
using TeleBot.Services;
var builder = Host.CreateApplicationBuilder(args);
public static string BrandName ?? "Little Shop";
var builder = WebApplication.CreateBuilder(args);
// Configuration
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
@@ -23,6 +24,9 @@ builder.Configuration
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
// Add MVC Controllers for webhook endpoints
builder.Services.AddControllers();
// Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
@@ -49,7 +53,8 @@ builder.Services.AddLittleShopClient(options =>
options.TimeoutSeconds = 30;
options.MaxRetryAttempts = 3;
BrandName = config["LittleShop.BrandName"] ?? "Little Shop";
// Set the brand name globally
BotConfig.BrandName = config["LittleShop:BrandName"] ?? "Little Shop";
});
builder.Services.AddSingleton<ILittleShopService, LittleShopService>();
@@ -80,10 +85,10 @@ builder.Services.AddSingleton<ICommandHandler, CommandHandler>();
builder.Services.AddSingleton<ICallbackHandler, CallbackHandler>();
builder.Services.AddSingleton<IMessageHandler, MessageHandler>();
// Bot Manager Service (for registration and metrics)
// Bot Manager Service (for registration and metrics) - Single instance
builder.Services.AddHttpClient<BotManagerService>();
builder.Services.AddSingleton<BotManagerService>();
builder.Services.AddHostedService<BotManagerService>();
builder.Services.AddHostedService(provider => provider.GetRequiredService<BotManagerService>());
// Message Delivery Service - Single instance
builder.Services.AddSingleton<MessageDeliveryService>();
@@ -94,11 +99,26 @@ builder.Services.AddHostedService<MessageDeliveryService>(sp => sp.GetRequiredSe
builder.Services.AddHttpClient<ProductCarouselService>();
builder.Services.AddSingleton<IProductCarouselService, ProductCarouselService>();
// Bot Service
builder.Services.AddHostedService<TelegramBotService>();
// Bot Service - Single instance
builder.Services.AddSingleton<TelegramBotService>();
builder.Services.AddHostedService(provider => provider.GetRequiredService<TelegramBotService>());
// Build and run
var host = builder.Build();
// Build the application
var app = builder.Build();
// Connect the services
var botManagerService = app.Services.GetRequiredService<BotManagerService>();
var telegramBotService = app.Services.GetRequiredService<TelegramBotService>();
botManagerService.SetTelegramBotService(telegramBotService);
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.MapControllers();
try
{
@@ -106,8 +126,9 @@ try
Log.Information("Privacy Mode: {PrivacyMode}", builder.Configuration["Privacy:Mode"]);
Log.Information("Ephemeral by Default: {Ephemeral}", builder.Configuration["Privacy:EphemeralByDefault"]);
Log.Information("Tor Enabled: {Tor}", builder.Configuration["Privacy:EnableTor"]);
await host.RunAsync();
Log.Information("Webhook endpoints available at /api/webhook");
await app.RunAsync();
}
catch (Exception ex)
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
@@ -20,9 +21,12 @@ namespace TeleBot.Services
private readonly SessionManager _sessionManager;
private Timer? _heartbeatTimer;
private Timer? _metricsTimer;
private Timer? _settingsSyncTimer;
private string? _botKey;
private Guid? _botId;
private readonly Dictionary<string, decimal> _metricsBuffer;
private TelegramBotService? _telegramBotService;
private string? _lastKnownBotToken;
public BotManagerService(
IConfiguration configuration,
@@ -37,6 +41,11 @@ namespace TeleBot.Services
_metricsBuffer = new Dictionary<string, decimal>();
}
public void SetTelegramBotService(TelegramBotService telegramBotService)
{
_telegramBotService = telegramBotService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
@@ -64,6 +73,9 @@ namespace TeleBot.Services
// Start metrics timer (every 60 seconds)
_metricsTimer = new Timer(SendMetrics, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
// Start settings sync timer (every 5 minutes)
_settingsSyncTimer = new Timer(SyncSettingsWithBotUpdate, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5));
_logger.LogInformation("Bot manager service started successfully");
}
catch (Exception ex)
@@ -76,10 +88,11 @@ namespace TeleBot.Services
{
_heartbeatTimer?.Change(Timeout.Infinite, 0);
_metricsTimer?.Change(Timeout.Infinite, 0);
_settingsSyncTimer?.Change(Timeout.Infinite, 0);
// Send final metrics before stopping
SendMetrics(null);
_logger.LogInformation("Bot manager service stopped");
return Task.CompletedTask;
}
@@ -161,23 +174,42 @@ namespace TeleBot.Services
{
if (string.IsNullOrEmpty(_botKey)) return;
var apiUrl = _configuration["LittleShop:ApiUrl"];
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("X-Bot-Key", _botKey);
var response = await _httpClient.GetAsync($"{apiUrl}/api/bots/settings");
if (response.IsSuccessStatusCode)
var settings = await GetSettingsAsync();
if (settings != null)
{
var settingsJson = await response.Content.ReadAsStringAsync();
var settings = JsonSerializer.Deserialize<Dictionary<string, object>>(settingsJson);
// Apply settings to configuration
// This would update the running configuration with server settings
_logger.LogInformation("Settings synced from server");
}
}
public async Task<Dictionary<string, object>?> GetSettingsAsync()
{
if (string.IsNullOrEmpty(_botKey)) return null;
try
{
var apiUrl = _configuration["LittleShop:ApiUrl"];
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("X-Bot-Key", _botKey);
var response = await _httpClient.GetAsync($"{apiUrl}/api/bots/settings");
if (response.IsSuccessStatusCode)
{
var settingsJson = await response.Content.ReadAsStringAsync();
var settings = JsonSerializer.Deserialize<Dictionary<string, object>>(settingsJson);
return settings;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to fetch settings from API");
}
return null;
}
private async void SendHeartbeat(object? state)
{
if (string.IsNullOrEmpty(_botKey)) return;
@@ -350,10 +382,45 @@ namespace TeleBot.Services
};
}
private async void SyncSettingsWithBotUpdate(object? state)
{
try
{
var settings = await GetSettingsAsync();
if (settings != null && settings.ContainsKey("telegram"))
{
if (settings["telegram"] is JsonElement telegramElement)
{
var telegramSettings = telegramElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value.ToString());
if (telegramSettings.TryGetValue("botToken", out var token))
{
// Check if token has changed
if (!string.IsNullOrEmpty(token) && token != _lastKnownBotToken)
{
_logger.LogInformation("Bot token has changed. Updating bot...");
_lastKnownBotToken = token;
// Update the TelegramBotService if available
if (_telegramBotService != null)
{
await _telegramBotService.UpdateBotTokenAsync(token);
}
}
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to sync settings with bot update");
}
}
public void Dispose()
{
_heartbeatTimer?.Dispose();
_metricsTimer?.Dispose();
_settingsSyncTimer?.Dispose();
}
// DTOs for API responses

View File

@@ -9,8 +9,10 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.InputFiles;
// InputFiles namespace no longer exists in newer Telegram.Bot versions
// using Telegram.Bot.Types.InputFiles;
using Telegram.Bot.Types.ReplyMarkups;
using TeleBot.UI;
namespace TeleBot.Services
{
@@ -18,7 +20,7 @@ namespace TeleBot.Services
{
Task SendProductCarouselAsync(ITelegramBotClient botClient, long chatId, PagedResult<Product> products, string? categoryName = null, int currentPage = 1);
Task SendSingleProductWithImageAsync(ITelegramBotClient botClient, long chatId, Product product);
Task<InputOnlineFile?> GetProductImageAsync(Product product);
Task<InputFile?> GetProductImageAsync(Product product);
Task<bool> IsImageUrlValidAsync(string imageUrl);
}
@@ -234,7 +236,7 @@ namespace TeleBot.Services
}
}
public async Task<InputOnlineFile?> GetProductImageAsync(Product product)
public async Task<InputFile?> GetProductImageAsync(Product product)
{
try
{
@@ -256,16 +258,16 @@ namespace TeleBot.Services
var cacheKey = $"{product.Id}_{photo.Id}";
var cachedPath = Path.Combine(_imageCachePath, $"{cacheKey}.jpg");
if (File.Exists(cachedPath))
if (System.IO.File.Exists(cachedPath))
{
return new InputOnlineFile(File.OpenRead(cachedPath), $"{product.Name}.jpg");
return InputFile.FromStream(System.IO.File.OpenRead(cachedPath), $"{product.Name}.jpg");
}
// Download and cache the image
var imageBytes = await _httpClient.GetByteArrayAsync(imageUrl);
await File.WriteAllBytesAsync(cachedPath, imageBytes);
await System.IO.File.WriteAllBytesAsync(cachedPath, imageBytes);
return new InputOnlineFile(File.OpenRead(cachedPath), $"{product.Name}.jpg");
return InputFile.FromStream(System.IO.File.OpenRead(cachedPath), $"{product.Name}.jpg");
}
catch (Exception ex)
{
@@ -281,7 +283,8 @@ namespace TeleBot.Services
if (string.IsNullOrEmpty(imageUrl))
return false;
var response = await _httpClient.HeadAsync(imageUrl);
using var request = new HttpRequestMessage(HttpMethod.Head, imageUrl);
var response = await _httpClient.SendAsync(request);
return response.IsSuccessStatusCode &&
response.Content.Headers.ContentType?.MediaType?.StartsWith("image/") == true;
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
@@ -24,9 +26,11 @@ namespace TeleBot
private readonly ICallbackHandler _callbackHandler;
private readonly IMessageHandler _messageHandler;
private readonly IMessageDeliveryService _messageDeliveryService;
private readonly BotManagerService _botManagerService;
private ITelegramBotClient? _botClient;
private CancellationTokenSource? _cancellationTokenSource;
private string? _currentBotToken;
public TelegramBotService(
IConfiguration configuration,
ILogger<TelegramBotService> logger,
@@ -34,7 +38,8 @@ namespace TeleBot
ICommandHandler commandHandler,
ICallbackHandler callbackHandler,
IMessageHandler messageHandler,
IMessageDeliveryService messageDeliveryService)
IMessageDeliveryService messageDeliveryService,
BotManagerService botManagerService)
{
_configuration = configuration;
_logger = logger;
@@ -43,16 +48,27 @@ namespace TeleBot
_callbackHandler = callbackHandler;
_messageHandler = messageHandler;
_messageDeliveryService = messageDeliveryService;
_botManagerService = botManagerService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var botToken = _configuration["Telegram:BotToken"];
// Try to get bot token from API first via BotManagerService
var botToken = await GetBotTokenAsync();
// Fallback to configuration if API doesn't provide token
if (string.IsNullOrEmpty(botToken))
{
botToken = _configuration["Telegram:BotToken"];
}
if (string.IsNullOrEmpty(botToken) || botToken == "YOUR_BOT_TOKEN_HERE")
{
_logger.LogError("Bot token not configured. Please set Telegram:BotToken in appsettings.json");
_logger.LogError("Bot token not configured. Please either register via admin panel or set Telegram:BotToken in appsettings.json");
return;
}
_currentBotToken = botToken;
_botClient = new TelegramBotClient(botToken);
_cancellationTokenSource = new CancellationTokenSource();
@@ -128,9 +144,82 @@ namespace TeleBot
ApiRequestException apiException => $"Telegram API Error: [{apiException.ErrorCode}] {apiException.Message}",
_ => exception.ToString()
};
_logger.LogError(exception, "Bot error: {ErrorMessage}", errorMessage);
return Task.CompletedTask;
}
private async Task<string?> GetBotTokenAsync()
{
try
{
// Check if we have a bot key stored
var botKey = _configuration["BotManager:ApiKey"];
if (string.IsNullOrEmpty(botKey))
{
_logger.LogInformation("No bot key configured. Bot will need to register first or use local token.");
return null;
}
// Fetch settings from API
var settings = await _botManagerService.GetSettingsAsync();
if (settings != null && settings.ContainsKey("telegram"))
{
if (settings["telegram"] is JsonElement telegramElement)
{
var telegramSettings = telegramElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value.ToString());
if (telegramSettings.TryGetValue("botToken", out var token))
{
_logger.LogInformation("Bot token fetched from admin panel successfully");
return token;
}
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch bot token from API. Will use local configuration.");
}
return null;
}
public async Task UpdateBotTokenAsync(string newToken)
{
if (_botClient != null && _currentBotToken != newToken)
{
_logger.LogInformation("Updating bot token and restarting bot...");
// Stop current bot
_cancellationTokenSource?.Cancel();
// Create new bot client with new token
_currentBotToken = newToken;
_botClient = new TelegramBotClient(newToken);
_cancellationTokenSource = new CancellationTokenSource();
var receiverOptions = new ReceiverOptions
{
AllowedUpdates = Array.Empty<UpdateType>(),
ThrowPendingUpdates = true
};
_botClient.StartReceiving(
HandleUpdateAsync,
HandleErrorAsync,
receiverOptions,
cancellationToken: _cancellationTokenSource.Token
);
var me = await _botClient.GetMeAsync();
_logger.LogInformation("Bot restarted with new token: @{Username} ({Id})", me.Username, me.Id);
// Update message delivery service
if (_messageDeliveryService is MessageDeliveryService deliveryService)
{
deliveryService.SetBotClient(_botClient);
}
}
}
}
}

View File

@@ -59,7 +59,7 @@ namespace TeleBot
{
Id = Guid.NewGuid(),
Url = "https://via.placeholder.com/300x200.jpg",
IsMain = true
SortOrder = 0 // Use SortOrder = 0 to indicate main photo
}
}
};

View File

@@ -328,13 +328,50 @@ namespace TeleBot.UI
{
return new InlineKeyboardMarkup(new[]
{
new[] {
InlineKeyboardButton.WithCallbackData("🛒 Buy Now", $"add:{productId}:1"),
new[] {
InlineKeyboardButton.WithCallbackData("🛒 Quick Buy", $"quickbuy:{productId}:1"),
InlineKeyboardButton.WithCallbackData("📄 Details", $"product:{productId}")
}
});
}
public static InlineKeyboardMarkup ProductVariationsMenu(Product product, int defaultQuantity = 1)
{
var buttons = new List<InlineKeyboardButton[]>();
if (product.Variations?.Any() == true)
{
// Add a button for each variation
foreach (var variation in product.Variations.OrderBy(v => v.Quantity))
{
var label = variation.Quantity > 1
? $"{variation.Name} - ${variation.Price:F2} (${variation.PricePerUnit:F2}/ea)"
: $"{variation.Name} - ${variation.Price:F2}";
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData(label, $"add:{product.Id}:{variation.Quantity}:{variation.Id}")
});
}
}
else
{
// No variations, just show regular add to cart
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData($"Add to Cart - ${product.Price:F2}", $"add:{product.Id}:{defaultQuantity}")
});
}
// Add back button
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData("⬅️ Back", "menu")
});
return new InlineKeyboardMarkup(buttons);
}
public static InlineKeyboardMarkup CategoryNavigationMenu(Guid? categoryId)
{
return new InlineKeyboardMarkup(new[]

View File

@@ -12,13 +12,13 @@ namespace TeleBot.UI
{
if (isReturning)
{
return $"🔒 *Welcome back to {Program.BrandName}*\n\n" +
return $"🔒 *Welcome back to {BotConfig.BrandName}*\n\n" +
"Your privacy is our priority. All sessions are ephemeral by default.\n\n" +
"🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" +
"How can I help you today?";
}
return $"🔒 *Welcome to {Program.BrandName}*\n\n" +
return $"🔒 *Welcome to {BotConfig.BrandName}*\n\n" +
"🛡️ *Your Privacy Matters:*\n" +
"• No account required\n" +
"• Ephemeral sessions by default\n" +
@@ -83,43 +83,93 @@ namespace TeleBot.UI
public static string FormatSingleProduct(Product product)
{
var sb = new StringBuilder();
sb.AppendLine($"🛍️ *{product.Name}*");
sb.AppendLine($"💰 £{product.Price:F2}");
// Show variations if available
if (product.Variations?.Any() == true)
{
var lowestPrice = product.Variations.Min(v => v.PricePerUnit);
sb.AppendLine($"💰 From £{lowestPrice:F2}");
sb.AppendLine($"📦 _{product.Variations.Count} options available_");
}
else
{
sb.AppendLine($"💰 £{product.Price:F2}");
}
if (!string.IsNullOrEmpty(product.Description))
{
// Truncate description for bubble format
var desc = product.Description.Length > 100
var desc = product.Description.Length > 100
? product.Description.Substring(0, 100) + "..."
: product.Description;
sb.AppendLine($"\n_{desc}_");
}
return sb.ToString();
}
public static string FormatProductDetail(Product product)
{
var sb = new StringBuilder();
sb.AppendLine($"🛍️ *{product.Name}*\n");
sb.AppendLine($"💰 *Price:* ${product.Price:F2}");
sb.AppendLine($"⚖️ *Weight:* {product.Weight} {product.WeightUnit}");
sb.AppendLine($"📁 *Category:* {product.CategoryName ?? "Uncategorized"}");
if (!string.IsNullOrEmpty(product.Description))
{
sb.AppendLine($"\n📝 *Description:*\n{product.Description}");
}
if (product.Photos.Any())
{
sb.AppendLine($"\n🖼 _{product.Photos.Count} photo(s) available_");
}
sb.AppendLine("\nSelect quantity and add to cart:");
return sb.ToString();
}
public static string FormatProductWithVariations(Product product)
{
var sb = new StringBuilder();
sb.AppendLine($"🛍️ *{product.Name}*\n");
if (product.Variations?.Any() == true)
{
sb.AppendLine("📦 *Available Options:*\n");
foreach (var variation in product.Variations.OrderBy(v => v.Quantity))
{
var savings = variation.Quantity > 1
? $" (${variation.PricePerUnit:F2} each)"
: "";
sb.AppendLine($"• *{variation.Name}*: ${variation.Price:F2}{savings}");
if (!string.IsNullOrEmpty(variation.Description))
{
sb.AppendLine($" _{variation.Description}_");
}
}
}
else
{
sb.AppendLine($"💰 *Price:* ${product.Price:F2}");
}
sb.AppendLine($"\n⚖ *Weight:* {product.Weight} {product.WeightUnit}");
sb.AppendLine($"📁 *Category:* {product.CategoryName ?? "Uncategorized"}");
if (!string.IsNullOrEmpty(product.Description))
{
sb.AppendLine($"\n📝 *Description:*\n{product.Description}");
}
sb.AppendLine("\n*Select an option to add to cart:*");
return sb.ToString();
}
@@ -275,7 +325,7 @@ namespace TeleBot.UI
"/cancel - Cancel current operation\n" +
"/delete - Delete all your data\n" +
"/tor - Get Tor onion address\n" +
"/help - Show this help message\n\n"
"/help - Show this help message\n\n";
}
public static string FormatPrivacyPolicy()

View File

@@ -6,16 +6,21 @@
},
"BotManager": {
"ApiKey": "",
"Comment": "This will be populated after first registration"
"Comment": "This will be populated after first registration with admin panel"
},
"Telegram": {
"BotToken": "7880403661:AAGma1wAyoHsmG45iO6VvHCqzimhJX1pp14",
"BotToken": "",
"AdminChatId": "",
"WebhookUrl": "",
"UseWebhook": false
"UseWebhook": false,
"Comment": "Bot token will be fetched from admin panel API if BotManager:ApiKey is set"
},
"Webhook": {
"Secret": "",
"Comment": "Optional secret key for webhook authentication"
},
"LittleShop": {
"ApiUrl": "https://localhost:5001",
"ApiUrl": "http://localhost:8080",
"OnionUrl": "",
"Username": "admin",
"Password": "admin",
@@ -66,11 +71,14 @@
"Cryptocurrencies": [
"BTC",
"XMR",
"USDT",
"LTC",
"ETH",
"ZEC",
"DASH",
"DOGE"
]
"DASH"
],
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5010"
}
}
}
}

253
TeleBot/deploy-bot.sh Executable file
View File

@@ -0,0 +1,253 @@
#!/bin/bash
# LittleShop TeleBot Docker Deployment Script
# Usage: ./deploy-bot.sh [OPTIONS]
#
# This script helps deploy TeleBot instances to local or remote Docker hosts
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
DOCKER_IMAGE="littleshop/telebot:latest"
CONTAINER_PREFIX="littleshop-bot"
API_URL="${LITTLESHOP_API_URL:-http://localhost:8080}"
RESTART_POLICY="unless-stopped"
DOCKER_HOST=""
# Function to print colored output
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# Function to show usage
show_usage() {
cat << EOF
Usage: $0 [OPTIONS]
Deploy LittleShop TeleBot to Docker hosts
OPTIONS:
-n, --name NAME Bot container name (required)
-t, --token TOKEN Telegram bot token (required)
-k, --api-key KEY Bot API key from admin panel
-a, --api-url URL LittleShop API URL (default: $API_URL)
-h, --host HOST Docker host (e.g., ssh://user@host or tcp://host:2376)
-c, --chat-id ID Admin chat ID for notifications
-e, --encryption-key KEY Database encryption key
-p, --personality NAME Bot personality name
-m, --mode MODE Privacy mode (strict|moderate|relaxed)
-i, --image IMAGE Docker image (default: $DOCKER_IMAGE)
-r, --restart POLICY Restart policy (default: $RESTART_POLICY)
-d, --detach Run in detached mode (default)
-l, --logs Follow logs after deployment
--pull Pull latest image before deployment
--rm Remove existing container before deployment
--help Show this help message
EXAMPLES:
# Deploy to local Docker
$0 -n support-bot -t "TOKEN" -k "API_KEY"
# Deploy to remote host via SSH
$0 -n sales-bot -t "TOKEN" -k "API_KEY" -h ssh://user@server.com
# Deploy with all options
$0 -n vip-bot -t "TOKEN" -k "API_KEY" -a https://api.shop.com \\
-c "123456789" -e "32_char_encryption_key_here" \\
-p "Sarah" -m strict --pull --rm
# Deploy multiple bots using environment file
source .env.bot1 && $0 -n bot1 -t "\$BOT_TOKEN" -k "\$API_KEY"
source .env.bot2 && $0 -n bot2 -t "\$BOT_TOKEN" -k "\$API_KEY"
EOF
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name)
BOT_NAME="$2"
shift 2
;;
-t|--token)
BOT_TOKEN="$2"
shift 2
;;
-k|--api-key)
API_KEY="$2"
shift 2
;;
-a|--api-url)
API_URL="$2"
shift 2
;;
-h|--host)
DOCKER_HOST="$2"
shift 2
;;
-c|--chat-id)
ADMIN_CHAT_ID="$2"
shift 2
;;
-e|--encryption-key)
ENCRYPTION_KEY="$2"
shift 2
;;
-p|--personality)
PERSONALITY="$2"
shift 2
;;
-m|--mode)
PRIVACY_MODE="$2"
shift 2
;;
-i|--image)
DOCKER_IMAGE="$2"
shift 2
;;
-r|--restart)
RESTART_POLICY="$2"
shift 2
;;
-l|--logs)
FOLLOW_LOGS=true
shift
;;
--pull)
PULL_IMAGE=true
shift
;;
--rm)
REMOVE_EXISTING=true
shift
;;
--help)
show_usage
exit 0
;;
*)
print_message "$RED" "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Validate required parameters
if [ -z "$BOT_NAME" ]; then
print_message "$RED" "Error: Bot name is required (-n/--name)"
exit 1
fi
if [ -z "$BOT_TOKEN" ]; then
print_message "$RED" "Error: Bot token is required (-t/--token)"
exit 1
fi
# Set container name
CONTAINER_NAME="${CONTAINER_PREFIX}-${BOT_NAME}"
# Build Docker command
DOCKER_CMD="docker"
if [ -n "$DOCKER_HOST" ]; then
export DOCKER_HOST="$DOCKER_HOST"
print_message "$YELLOW" "Deploying to remote host: $DOCKER_HOST"
fi
# Pull latest image if requested
if [ "$PULL_IMAGE" = true ]; then
print_message "$YELLOW" "Pulling latest image: $DOCKER_IMAGE"
$DOCKER_CMD pull "$DOCKER_IMAGE"
fi
# Remove existing container if requested
if [ "$REMOVE_EXISTING" = true ]; then
if $DOCKER_CMD ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
print_message "$YELLOW" "Removing existing container: $CONTAINER_NAME"
$DOCKER_CMD rm -f "$CONTAINER_NAME"
fi
fi
# Build environment variables
ENV_VARS=(
"-e DOTNET_ENVIRONMENT=Production"
"-e TZ=UTC"
"-e Telegram__BotToken=$BOT_TOKEN"
"-e LittleShop__ApiUrl=$API_URL"
"-e Database__ConnectionString=Filename=/app/data/telebot.db;Password=;"
)
# Add optional environment variables
[ -n "$API_KEY" ] && ENV_VARS+=("-e BotManager__ApiKey=$API_KEY")
[ -n "$ADMIN_CHAT_ID" ] && ENV_VARS+=("-e Telegram__AdminChatId=$ADMIN_CHAT_ID")
[ -n "$ENCRYPTION_KEY" ] && ENV_VARS+=("-e Database__EncryptionKey=$ENCRYPTION_KEY")
[ -n "$PERSONALITY" ] && ENV_VARS+=("-e Bot__PersonalityName=$PERSONALITY")
[ -n "$PRIVACY_MODE" ] && ENV_VARS+=("-e Privacy__Mode=$PRIVACY_MODE")
# Default privacy settings
ENV_VARS+=(
"-e Privacy__DataRetentionHours=24"
"-e Privacy__SessionTimeoutMinutes=30"
"-e Privacy__EphemeralByDefault=true"
"-e Features__EnableQRCodes=true"
"-e Features__EnablePGPEncryption=true"
"-e Features__EnableDisappearingMessages=true"
)
# Build volumes
VOLUMES=(
"-v ${CONTAINER_NAME}-data:/app/data"
"-v ${CONTAINER_NAME}-logs:/app/logs"
)
# Deploy the container
print_message "$GREEN" "Deploying bot: $CONTAINER_NAME"
print_message "$YELLOW" "API URL: $API_URL"
$DOCKER_CMD run -d \
--name "$CONTAINER_NAME" \
--restart "$RESTART_POLICY" \
"${ENV_VARS[@]}" \
"${VOLUMES[@]}" \
"$DOCKER_IMAGE"
# Check if deployment was successful
if [ $? -eq 0 ]; then
print_message "$GREEN" "✅ Bot deployed successfully: $CONTAINER_NAME"
# Get container info
CONTAINER_ID=$($DOCKER_CMD ps -q -f name="$CONTAINER_NAME")
print_message "$YELLOW" "Container ID: $CONTAINER_ID"
# Show container status
$DOCKER_CMD ps -f name="$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Follow logs if requested
if [ "$FOLLOW_LOGS" = true ]; then
print_message "$YELLOW" "Following logs (Ctrl+C to exit)..."
$DOCKER_CMD logs -f "$CONTAINER_NAME"
else
print_message "$YELLOW" "View logs with: docker logs -f $CONTAINER_NAME"
fi
else
print_message "$RED" "❌ Deployment failed"
exit 1
fi
# Print next steps
echo ""
print_message "$GREEN" "Next steps:"
echo "1. Check bot status: docker ps -f name=$CONTAINER_NAME"
echo "2. View logs: docker logs $CONTAINER_NAME"
echo "3. Stop bot: docker stop $CONTAINER_NAME"
echo "4. Remove bot: docker rm -f $CONTAINER_NAME"
echo "5. Access admin panel to manage bot settings"

View File

@@ -0,0 +1,284 @@
version: '3.8'
services:
# Customer Support Bot
bot-support:
image: littleshop/telebot:latest
container_name: littleshop-bot-support
restart: unless-stopped
environment:
- DOTNET_ENVIRONMENT=Production
- TZ=UTC
# Telegram Configuration
- Telegram__BotToken=${SUPPORT_BOT_TOKEN}
- Telegram__AdminChatId=${SUPPORT_ADMIN_CHAT_ID}
- Telegram__UseWebhook=false
# LittleShop API Configuration
- LittleShop__ApiUrl=${LITTLESHOP_API_URL:-http://host.docker.internal:8080}
- LittleShop__Username=${LITTLESHOP_USERNAME:-admin}
- LittleShop__Password=${LITTLESHOP_PASSWORD:-admin}
- BotManager__ApiKey=${SUPPORT_BOT_API_KEY}
# Privacy Settings
- Privacy__Mode=strict
- Privacy__DataRetentionHours=24
- Privacy__SessionTimeoutMinutes=30
# Database Configuration
- Database__ConnectionString=Filename=/app/data/telebot.db;Password=;
- Database__EncryptionKey=${SUPPORT_DB_ENCRYPTION_KEY}
# Features
- Features__EnableQRCodes=true
- Features__EnablePGPEncryption=true
- Features__EnableDisappearingMessages=true
volumes:
- support-bot-data:/app/data
- support-bot-logs:/app/logs
networks:
- littleshop-network
healthcheck:
test: ["CMD", "pgrep", "-f", "dotnet.*TeleBot"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Sales & Marketing Bot
bot-sales:
image: littleshop/telebot:latest
container_name: littleshop-bot-sales
restart: unless-stopped
environment:
- DOTNET_ENVIRONMENT=Production
- TZ=UTC
# Telegram Configuration
- Telegram__BotToken=${SALES_BOT_TOKEN}
- Telegram__AdminChatId=${SALES_ADMIN_CHAT_ID}
- Telegram__UseWebhook=false
# LittleShop API Configuration
- LittleShop__ApiUrl=${LITTLESHOP_API_URL:-http://host.docker.internal:8080}
- LittleShop__Username=${LITTLESHOP_USERNAME:-admin}
- LittleShop__Password=${LITTLESHOP_PASSWORD:-admin}
- BotManager__ApiKey=${SALES_BOT_API_KEY}
# Privacy Settings
- Privacy__Mode=moderate
- Privacy__DataRetentionHours=72
- Privacy__SessionTimeoutMinutes=60
- Privacy__EnableAnalytics=true
# Database Configuration
- Database__ConnectionString=Filename=/app/data/telebot.db;Password=;
- Database__EncryptionKey=${SALES_DB_ENCRYPTION_KEY}
# Features
- Features__EnableQRCodes=true
- Features__EnablePGPEncryption=false
- Features__EnableDisappearingMessages=false
volumes:
- sales-bot-data:/app/data
- sales-bot-logs:/app/logs
networks:
- littleshop-network
healthcheck:
test: ["CMD", "pgrep", "-f", "dotnet.*TeleBot"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# VIP/Premium Customer Bot
bot-vip:
image: littleshop/telebot:latest
container_name: littleshop-bot-vip
restart: unless-stopped
environment:
- DOTNET_ENVIRONMENT=Production
- TZ=UTC
# Telegram Configuration
- Telegram__BotToken=${VIP_BOT_TOKEN}
- Telegram__AdminChatId=${VIP_ADMIN_CHAT_ID}
- Telegram__UseWebhook=false
# LittleShop API Configuration
- LittleShop__ApiUrl=${LITTLESHOP_API_URL:-http://host.docker.internal:8080}
- LittleShop__Username=${LITTLESHOP_USERNAME:-admin}
- LittleShop__Password=${LITTLESHOP_PASSWORD:-admin}
- BotManager__ApiKey=${VIP_BOT_API_KEY}
# Privacy Settings (Enhanced for VIP)
- Privacy__Mode=strict
- Privacy__DataRetentionHours=1
- Privacy__SessionTimeoutMinutes=15
- Privacy__EnableAnalytics=false
- Privacy__RequirePGPForShipping=true
- Privacy__EphemeralByDefault=true
- Privacy__EnableTor=${VIP_ENABLE_TOR:-false}
# Database Configuration
- Database__ConnectionString=Filename=/app/data/telebot.db;Password=;
- Database__EncryptionKey=${VIP_DB_ENCRYPTION_KEY}
# Features (All features for VIP)
- Features__EnableVoiceSearch=true
- Features__EnableQRCodes=true
- Features__EnablePGPEncryption=true
- Features__EnableDisappearingMessages=true
- Features__EnableOrderMixing=true
- Features__MixingDelayMinSeconds=60
- Features__MixingDelayMaxSeconds=300
volumes:
- vip-bot-data:/app/data
- vip-bot-logs:/app/logs
networks:
- littleshop-network
healthcheck:
test: ["CMD", "pgrep", "-f", "dotnet.*TeleBot"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Regional Bot Example (EU)
bot-eu:
image: littleshop/telebot:latest
container_name: littleshop-bot-eu
restart: unless-stopped
environment:
- DOTNET_ENVIRONMENT=Production
- TZ=Europe/London
# Telegram Configuration
- Telegram__BotToken=${EU_BOT_TOKEN}
- Telegram__AdminChatId=${EU_ADMIN_CHAT_ID}
- Telegram__UseWebhook=false
# LittleShop API Configuration
- LittleShop__ApiUrl=${EU_API_URL:-http://host.docker.internal:8080}
- LittleShop__Username=${LITTLESHOP_USERNAME:-admin}
- LittleShop__Password=${LITTLESHOP_PASSWORD:-admin}
- BotManager__ApiKey=${EU_BOT_API_KEY}
# Privacy Settings (GDPR Compliant)
- Privacy__Mode=strict
- Privacy__DataRetentionHours=24
- Privacy__SessionTimeoutMinutes=30
- Privacy__EnableAnalytics=false
- Privacy__EphemeralByDefault=true
# Database Configuration
- Database__ConnectionString=Filename=/app/data/telebot.db;Password=;
- Database__EncryptionKey=${EU_DB_ENCRYPTION_KEY}
# Features
- Features__EnableQRCodes=true
- Features__EnablePGPEncryption=true
- Features__EnableDisappearingMessages=true
volumes:
- eu-bot-data:/app/data
- eu-bot-logs:/app/logs
networks:
- littleshop-network
healthcheck:
test: ["CMD", "pgrep", "-f", "dotnet.*TeleBot"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Optional: Shared Redis for all bots
redis:
image: redis:7-alpine
container_name: littleshop-redis-shared
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- littleshop-network
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 30s
timeout: 3s
retries: 5
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
support-bot-data:
name: littleshop-bot-support-data
support-bot-logs:
name: littleshop-bot-support-logs
sales-bot-data:
name: littleshop-bot-sales-data
sales-bot-logs:
name: littleshop-bot-sales-logs
vip-bot-data:
name: littleshop-bot-vip-data
vip-bot-logs:
name: littleshop-bot-vip-logs
eu-bot-data:
name: littleshop-bot-eu-data
eu-bot-logs:
name: littleshop-bot-eu-logs
redis-data:
name: littleshop-redis-shared-data
networks:
littleshop-network:
name: littleshop-network
driver: bridge

View File

@@ -0,0 +1,295 @@
{
"version": "2",
"templates": [
{
"type": 1,
"title": "LittleShop TeleBot",
"name": "littleshop-telebot",
"description": "Deploy a Telegram bot for LittleShop e-commerce platform",
"note": "Requires a Telegram bot token from @BotFather and API key from LittleShop admin panel",
"categories": ["bots", "ecommerce", "telegram"],
"platform": "linux",
"logo": "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/png/telegram.png",
"image": "littleshop/telebot:latest",
"restart_policy": "unless-stopped",
"network_mode": "bridge",
"hostname": "",
"privileged": false,
"interactive": false,
"env": [
{
"name": "DOTNET_ENVIRONMENT",
"label": "Environment",
"default": "Production",
"preset": true
},
{
"name": "TZ",
"label": "Timezone",
"default": "UTC",
"description": "Container timezone (e.g., Europe/London, America/New_York)"
},
{
"name": "Telegram__BotToken",
"label": "Telegram Bot Token",
"description": "Token from @BotFather (required)"
},
{
"name": "Telegram__AdminChatId",
"label": "Admin Chat ID",
"description": "Telegram chat ID for admin notifications (optional)"
},
{
"name": "LittleShop__ApiUrl",
"label": "LittleShop API URL",
"default": "http://host.docker.internal:8080",
"description": "URL to LittleShop API (e.g., https://api.shop.com)"
},
{
"name": "LittleShop__Username",
"label": "API Username",
"default": "admin",
"description": "LittleShop API username"
},
{
"name": "LittleShop__Password",
"label": "API Password",
"default": "admin",
"description": "LittleShop API password"
},
{
"name": "BotManager__ApiKey",
"label": "Bot API Key",
"description": "API key from LittleShop admin panel (optional)"
},
{
"name": "Database__EncryptionKey",
"label": "Database Encryption Key",
"description": "32-character key for database encryption (auto-generated if not provided)"
},
{
"name": "Privacy__Mode",
"label": "Privacy Mode",
"default": "strict",
"select": [
{
"text": "Strict (Maximum Privacy)",
"value": "strict"
},
{
"text": "Moderate (Balanced)",
"value": "moderate"
},
{
"text": "Relaxed (More Features)",
"value": "relaxed"
}
],
"description": "Privacy level for user data handling"
},
{
"name": "Privacy__DataRetentionHours",
"label": "Data Retention Hours",
"default": "24",
"description": "Hours to retain user data (1-168)"
},
{
"name": "Privacy__SessionTimeoutMinutes",
"label": "Session Timeout Minutes",
"default": "30",
"description": "Minutes before session expires (5-1440)"
},
{
"name": "Privacy__EphemeralByDefault",
"label": "Ephemeral Messages",
"default": "true",
"select": [
{
"text": "Enabled",
"value": "true"
},
{
"text": "Disabled",
"value": "false"
}
],
"description": "Enable ephemeral messages by default"
},
{
"name": "Features__EnableQRCodes",
"label": "Enable QR Codes",
"default": "true",
"select": [
{
"text": "Enabled",
"value": "true"
},
{
"text": "Disabled",
"value": "false"
}
]
},
{
"name": "Features__EnablePGPEncryption",
"label": "Enable PGP Encryption",
"default": "true",
"select": [
{
"text": "Enabled",
"value": "true"
},
{
"text": "Disabled",
"value": "false"
}
]
},
{
"name": "Features__EnableDisappearingMessages",
"label": "Enable Disappearing Messages",
"default": "true",
"select": [
{
"text": "Enabled",
"value": "true"
},
{
"text": "Disabled",
"value": "false"
}
]
}
],
"volumes": [
{
"container": "/app/data",
"bind": "!telebot-data"
},
{
"container": "/app/logs",
"bind": "!telebot-logs"
}
],
"labels": [
{
"name": "traefik.enable",
"value": "false"
},
{
"name": "com.littleshop.bot",
"value": "true"
}
]
},
{
"type": 3,
"title": "LittleShop Multi-Bot Stack",
"name": "littleshop-multibot",
"description": "Deploy multiple LittleShop TeleBots (Support, Sales, VIP)",
"note": "Requires multiple bot tokens and configuration. Edit the stack after creation to add environment variables.",
"categories": ["bots", "ecommerce", "telegram"],
"platform": "linux",
"logo": "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/png/telegram.png",
"repository": {
"url": "https://github.com/yourusername/littleshop",
"stackfile": "TeleBot/docker-compose.multi.yml"
},
"env": [
{
"name": "LITTLESHOP_API_URL",
"label": "LittleShop API URL",
"default": "http://host.docker.internal:8080",
"description": "Shared API URL for all bots"
},
{
"name": "LITTLESHOP_USERNAME",
"label": "API Username",
"default": "admin"
},
{
"name": "LITTLESHOP_PASSWORD",
"label": "API Password",
"default": "admin"
},
{
"name": "SUPPORT_BOT_TOKEN",
"label": "Support Bot Token",
"description": "Token for customer support bot"
},
{
"name": "SUPPORT_BOT_API_KEY",
"label": "Support Bot API Key",
"description": "API key for support bot"
},
{
"name": "SUPPORT_ADMIN_CHAT_ID",
"label": "Support Admin Chat ID"
},
{
"name": "SUPPORT_DB_ENCRYPTION_KEY",
"label": "Support DB Encryption Key",
"description": "32-char key for support bot database"
},
{
"name": "SALES_BOT_TOKEN",
"label": "Sales Bot Token",
"description": "Token for sales/marketing bot"
},
{
"name": "SALES_BOT_API_KEY",
"label": "Sales Bot API Key",
"description": "API key for sales bot"
},
{
"name": "SALES_ADMIN_CHAT_ID",
"label": "Sales Admin Chat ID"
},
{
"name": "SALES_DB_ENCRYPTION_KEY",
"label": "Sales DB Encryption Key",
"description": "32-char key for sales bot database"
},
{
"name": "VIP_BOT_TOKEN",
"label": "VIP Bot Token",
"description": "Token for VIP customer bot"
},
{
"name": "VIP_BOT_API_KEY",
"label": "VIP Bot API Key",
"description": "API key for VIP bot"
},
{
"name": "VIP_ADMIN_CHAT_ID",
"label": "VIP Admin Chat ID"
},
{
"name": "VIP_DB_ENCRYPTION_KEY",
"label": "VIP DB Encryption Key",
"description": "32-char key for VIP bot database"
},
{
"name": "VIP_ENABLE_TOR",
"label": "Enable Tor for VIP Bot",
"default": "false",
"select": [
{
"text": "Enabled",
"value": "true"
},
{
"text": "Disabled",
"value": "false"
}
]
},
{
"name": "REDIS_PASSWORD",
"label": "Redis Password",
"description": "Password for shared Redis cache"
}
]
}
]
}