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:
131
TeleBot/.env.multi.example
Normal file
131
TeleBot/.env.multi.example
Normal 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
|
||||
504
TeleBot/MULTI-HOST-DEPLOYMENT.md
Normal file
504
TeleBot/MULTI-HOST-DEPLOYMENT.md
Normal 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
|
||||
7
TeleBot/TeleBot/BotConfig.cs
Normal file
7
TeleBot/TeleBot/BotConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace TeleBot
|
||||
{
|
||||
public static class BotConfig
|
||||
{
|
||||
public static string BrandName { get; set; } = "Little Shop";
|
||||
}
|
||||
}
|
||||
101
TeleBot/TeleBot/Controllers/WebhookController.cs
Normal file
101
TeleBot/TeleBot/Controllers/WebhookController.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
253
TeleBot/deploy-bot.sh
Executable 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"
|
||||
284
TeleBot/docker-compose.multi.yml
Normal file
284
TeleBot/docker-compose.multi.yml
Normal 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
|
||||
295
TeleBot/portainer-template.json
Normal file
295
TeleBot/portainer-template.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user