diff --git a/.gitmodules b/.gitmodules index 071b750..7530951 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "telegram-join-approval-bot"] - path = telegram-join-approval-bot +[submodule "telegram-approval-join"] + path = telegram-approval-join url = ssh://git@git.zio.sh:2222/astra/telegram-join-approval-bot.git diff --git a/Dockerfile b/Dockerfile index 33ffec3..d55b48a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,14 +23,14 @@ RUN apk add --no-cache ca-certificates WORKDIR /opt # Copy binary into a standard location -COPY --from=builder /bin/telegram-join-approval-nuzzles /usr/local/bin/telegram-join-approval-bot +COPY --from=builder /bin/telegram-join-approval-nuzzles /usr/local/bin/telegram-approval-join # Create a non-root user and group with specific UID:GID and set ownership RUN addgroup -g 65532 app && \ adduser -D -H -u 65532 -G app -s /sbin/nologin app -h /opt && \ - chown -R app:app /opt /usr/local/bin/telegram-join-approval-bot + chown -R app:app /opt /usr/local/bin/telegram-approval-join # Run as the created non-root user USER app:app -ENTRYPOINT ["/usr/local/bin/telegram-join-approval-bot"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/telegram-approval-join"] \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 2b77a21..0000000 --- a/README.md +++ /dev/null @@ -1,301 +0,0 @@ -# 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] diff --git a/scripts/sync.sh b/scripts/sync.sh index 550827a..058fe58 100755 --- a/scripts/sync.sh +++ b/scripts/sync.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# sync.sh — Copy telegram-join-approval-bot submodule into internal/, then apply patches. +# sync.sh — Copy telegram-approval-join submodule into internal/, then apply patches. # Usage: ./scripts/sync.sh set -euo pipefail @@ -7,7 +7,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -SUBMODULE_DIR="$ROOT_DIR/telegram-join-approval-bot" +SUBMODULE_DIR="$ROOT_DIR/telegram-approval-join" PATCH_FILE="$ROOT_DIR/patches/0001-nuzzles.patch" # ── 1. Ensure submodule is initialised ──────────────────────────────────────── @@ -22,13 +22,19 @@ git -C "$ROOT_DIR" submodule update --init --recursive --remote # Capture the submodule commit after update NEW_COMMIT=$(git -C "$SUBMODULE_DIR" rev-parse HEAD 2>/dev/null || echo "") +# Check if there were any changes +# if [[ "$OLD_COMMIT" == "$NEW_COMMIT" ]] && [[ -n "$OLD_COMMIT" ]]; then +# echo " Submodule is already up to date. Nothing to do." +# exit 0 +# fi + # ── 2. Wipe and re-copy the submodule source ────────────────────────────────── -echo "→ Copying telegram-join-approval-bot source to root..." +echo "→ Copying telegram-approval-join source to root..." # Clean up directories that will be replaced rm -rf "$ROOT_DIR/cmd" "$ROOT_DIR/internal" "$ROOT_DIR/go.mod" "$ROOT_DIR/go.sum" "$ROOT_DIR/config.yaml.example" # Copy source files, excluding .git and keeping patches/ and scripts/ -rsync -a --stats --exclude='.git' --exclude='internal/telegram-join-approval-bot' \ +rsync -a --stats --exclude='.git' --exclude='internal/telegram-approval-join' \ "$SUBMODULE_DIR/" "$ROOT_DIR/" \ --exclude='patches' --exclude='scripts' --exclude='README.md' --exclude='Dockerfile' @@ -36,13 +42,13 @@ rsync -a --stats --exclude='.git' --exclude='internal/telegram-join-approval-bot sed -i '/^scripts\/$/d' "$ROOT_DIR/.gitignore" # ── 3. Rewrite the Go module path inside the copied source ──────────────────── -# Change the module from telegram-join-approval-bot to telegram-join-approval-nuzzles +# Change the module from telegram-approval-join to telegram-join-approval-nuzzles echo "→ Rewriting module path in go.mod ..." -sed -i "s|^module git\.zio\.sh/astra/telegram-join-approval-bot|module git.zio.sh/astra/telegram-join-approval-nuzzles|" "$ROOT_DIR/go.mod" +sed -i "s|^module git\.zio\.sh/astra/telegram-approval-join|module git.zio.sh/astra/telegram-join-approval-nuzzles|" "$ROOT_DIR/go.mod" # Fix all import references in the copied source echo "→ Rewriting import paths in .go files ..." -find "$ROOT_DIR" -name '*.go' -not -path "*/telegram-join-approval-bot/*" | xargs sed -i 's|git\.zio\.sh/astra/telegram-join-approval-bot|git.zio.sh/astra/telegram-join-approval-nuzzles|g' +find "$ROOT_DIR" -name '*.go' -not -path "*/telegram-approval-join/*" | xargs sed -i 's|git\.zio\.sh/astra/telegram-approval-join|git.zio.sh/astra/telegram-join-approval-nuzzles|g' # ── 4. Apply string patch ──────────────────────────────────────────── if [[ -f "$PATCH_FILE" ]]; then @@ -53,7 +59,7 @@ if [[ -f "$PATCH_FILE" ]]; then echo " Patch applied successfully." else echo "" - echo "⚠️ Patch did not apply cleanly — telegram-join-approval-bot may have changed." + echo "⚠️ Patch did not apply cleanly — telegram-approval-join may have changed." echo " Run the following to see conflicts:" echo " patch --dry-run -p1 -d $ROOT_DIR < $PATCH_FILE" echo "" @@ -71,7 +77,7 @@ go build ./... 2>&1 && echo " Build OK." || { echo "❌ Build failed."; exit 1 # ── 6. Commit changes ───────────────────────────────────────────────────────── echo "→ Committing changes..." git -C "$ROOT_DIR" add -A -git -C "$ROOT_DIR" commit -m "Update telegram-join-approval-bot submodule and apply patches" || true +git -C "$ROOT_DIR" commit -m "Update telegram-approval-join submodule and apply patches" || true echo "" -echo "✅ Sync complete. Root directory is up to date with telegram-join-approval-bot (patched)." \ No newline at end of file +echo "✅ Sync complete. Root directory is up to date with telegram-approval-join (patched)." \ No newline at end of file diff --git a/telegram-join-approval-bot b/telegram-approval-join similarity index 100% rename from telegram-join-approval-bot rename to telegram-approval-join