diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b77a21 --- /dev/null +++ b/README.md @@ -0,0 +1,301 @@ +# Telegram Join Approval Bot + +A Telegram bot for managing and approving join requests to private groups with approval workflows, canned responses, and admin controls. + +## Overview + +This bot automates the process of approving new members joining a private Telegram group. When a user requests to join, the bot: +1. Sends them a prompt to explain why they want to join +2. Forwards their request to the admin chat with approve/decline/ban buttons +3. Allows admins to manage requests with customizable decline reasons +4. Notifies users of the approval decision + +## Features + +- **Automated Join Requests**: Intercepts and manages join requests to your target group +- **Admin Approval Workflow**: Approve, decline, or ban users with a single click +- **Join Reason Collection**: Users must provide a reason before their request reaches admins +- **Canned Responses**: Pre-configured decline messages for common rejection reasons +- **Flexible Admin Interface**: + - Approve, decline, or ban users + - Add custom decline reasons + - Configure bot messages and settings + - Organize requests in topic threads +- **Approval Notifications**: Optional message sent to approved users +- **Configurable Messages**: Customize entry and approval messages +- **User Banning**: Ban users for 24 hours with a single action + +## How It Works + +### Join Request Flow + +``` +User requests to join group + ↓ +Bot sends entry prompt to user + ↓ +Bot notifies admins with join request + (showing user info and join reason placeholder) + ↓ +User sends their join reason + (message to bot in private chat) + ↓ +Bot updates admin message with user's reason + ↓ +Admin clicks Approve/Decline/Ban + ↓ +User notified of decision (if configured) + ↓ +User added/rejected/banned from group +``` + +### Architecture + +The bot consists of several key components: + +#### Main Entry Point (`main.go`) +- Initializes the bot with config from `config.yaml` +- Starts the long polling update loop +- Routes updates to appropriate handlers + +#### Configuration (`config/config.go`) +- Loads settings from `config.yaml` +- Auto-generates template config if missing +- Supports hot-reloading configuration changes + +#### Handlers +- **`join.go`**: Manages join request flow and user responses +- **`callbacks.go`**: Handles inline button actions (approve/decline/ban) +- **`admin.go`**: Processes admin commands for configuration +- **`handlers.go`**: Shared types and utilities + +#### Key Data Structures + +```go +// ExtendedChatJoinRequest tracks a user's approval request +type ExtendedChatJoinRequest struct { + *ChatJoinRequest // Telegram join request + JoinReason string // User's explanation + JoinRequestMessageID int // Admin notification message ID +} + +// Bot maintains runtime state +type Bot struct { + API BotAPI // Telegram API client + Config Config // Configuration + WaitingForApproval map // In-memory user request tracking +} +``` + +## Configuration + +The bot reads from `config.yaml` in its working directory. On first run, it creates a template: + +```yaml +bot_token: "YOUR_BOT_TOKEN_HERE" # @BotFather token +admin_chat_id: 0 # Chat ID where admins review requests +admin_chat_topic_id: 0 # Optional: Topic ID in admin chat (0 = main) +target_chat_id: 0 # Private group to protect +entry_message: "Please explain why..." # Prompt shown to joining users +approval_message: "" # Message sent after approval (optional) +send_approval_message: false # Whether to send approval message +delete_request_after_decision: false # Auto-delete admin messages after decision +canned_decline_responses: # Pre-configured decline reasons + - "Your profile doesn't meet our criteria" + - "We're at capacity" +``` + +### Getting Required IDs + +**Bot Token**: Create a bot with [@BotFather](https://t.me/botfather) + +**Chat IDs**: +```bash +# Forward a message from the chat to @getidsbot +# It will show you the chat ID +``` + +## Admin Commands + +Admins can use these commands in the admin chat: + +### `/setentrymessage ` +Change the message shown when users request to join. + +``` +/setentrymessage Tell us about yourself and why you want to join! +``` + +### `/setapprovalmessage ` +Set a message sent to approved users. Must be set before enabling approval notifications. + +``` +/setapprovalmessage Welcome to our group! We're excited to have you. +``` + +### `/togglesendapproval` +Enable/disable sending the approval message to approved users. + +### `/setadmintopic ` +Direct admin messages to a specific topic thread (0 = main chat). + +``` +/setadmintopic 42 +``` + +### `/info` +Display current bot configuration. + +### `/edit ` +Reply to a message and use this to edit it. Useful for correcting admin notifications. + +``` +/edit User was banned incorrectly +``` + +## Approval Workflow + +### Admin Actions + +When a user requests to join, an admin sees: + +``` +New join #request from @username [123456789] + +Join reason: "I'm interested in this community" +``` + +With inline buttons: +- **Approve** - Add user to group, optionally send approval message +- **Decline** - Reject request, show decline reason button panel +- **Ban** - Ban user for 24 hours (shows confirmation) + +### Decline Responses + +When declining with canned responses configured: + +1. Admin clicks **Decline** +2. Message shows available canned responses as buttons +3. Admin clicks a canned response +4. User receives the selected reason +5. Admin message updates with the reason + +Or, admin can reply to a decline message with custom text (with or without `/` prefix): + +``` +[Reply to decline message] +/We can only accept members with 2+ years experience +``` + +## Deployment + +### Docker + +Build and run the container: + +```bash +docker build -t telegram-approval-bot . +docker run -it \ + --mount type=bind,source=$PWD/config.yaml,target=/opt/config.yaml \ + telegram-approval-bot +``` + +The Dockerfile: +- Builds a static binary with minimal dependencies +- Uses Alpine for small image size +- Runs as unprivileged user (UID 65532) +- Mounts config as a volume for persistence + +### Compose Example + +```yaml +version: '3' +services: + bot: + build: . + volumes: + - ./config.yaml:/opt/config.yaml + restart: unless-stopped +``` + +## Implementation Details + +### State Management + +Join requests are tracked in memory using a mutex-protected map: + +```go +WaitingForApproval map[int64]*ExtendedChatJoinRequest +``` + +When a user provides their join reason, the bot: +1. Updates the request with their reason +2. Edits the admin message to show the reason +3. Keeps the message indexed by message ID for button handling + +### Update Processing + +The bot receives Telegram updates via long polling: + +1. **ChatJoinRequest**: User requests to join → calls `HandleJoinRequest` +2. **CallbackQuery**: Admin clicks button → calls `HandleCallbackQuery` +3. **Message**: User sends reason or admin command → routes accordingly + +### Message Formatting + +Admin notifications use HTML formatting with: +- User display name with link +- Numeric user ID +- Join reason (HTML-escaped for safety) +- Admin action history +- Timestamp information + +### Error Handling + +- Failed API requests restore the original message +- Missing user state after restart shows helpful error +- Invalid callback data is logged without crashing + +## Development + +### Dependencies + +- `github.com/OvyFlash/telegram-bot-api` - Telegram Bot API client +- `go.yaml.in/yaml/v3` - YAML configuration parsing + +### Go Version + +Requires Go 1.25.3+ + +### Building Locally + +```bash +go build -o telegram-approval-bot ./... +./telegram-approval-bot +``` + +## Troubleshooting + +### Bot doesn't respond to join requests +- Verify `target_chat_id` is correct +- Check bot has admin rights in the target group +- Ensure bot is configured to receive join request updates + +### Admin messages don't appear +- Verify `admin_chat_id` is correct +- Check bot has message permission in admin chat +- If using topics, verify `admin_chat_topic_id` is set + +### Approve/Decline buttons don't work +- Bot needs admin rights in target group +- Join request may have expired (Telegram timeout) +- Check logs for API errors + +### Config changes don't apply +- Some changes require bot restart +- Use `/info` to verify current settings +- Check config.yaml for syntax errors + +## License + +[Specify your license here]