discourse
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| discourse [2026/01/23 06:01] – created 208.92.105.181 | discourse [2026/01/26 21:07] (current) – 208.92.105.160 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ```markdown | + | ====== |
| - | # Discourse Installation on Fresh Ubuntu Server with Backup Restoration | + | |
| - | ## Prerequisites | + | ===== Prerequisites |
| - | - Fresh Ubuntu Server installation | + | |
| - | - Root or sudo access | + | |
| - | - Existing Discourse backup file (.tar.gz) | + | |
| - | - Domain name pointed to server IP | + | |
| - | ## Installation Steps | + | ===== Installation Steps ===== |
| - | ### 1. Initial System Setup | + | ==== 1. Initial System Setup ==== |
| - | ```bash | + | < |
| sudo -s | sudo -s | ||
| apt-get install git | apt-get install git | ||
| - | ``` | + | </ |
| - | ### 2. Clone Discourse Docker Repository | + | ==== 2. Clone Discourse Docker Repository |
| - | ```bash | + | < |
| git clone https:// | git clone https:// | ||
| cd / | cd / | ||
| chmod 700 containers | chmod 700 containers | ||
| - | ``` | + | </ |
| - | ### 3. Edit app.yml for Reverse Proxy Configuration | + | ==== 3. Edit app.yml for Reverse Proxy Configuration |
| - | **Before running discourse-setup**, | + | **Before running discourse-setup**, |
| - | #### Comment Out SSL Templates | + | === Comment Out SSL Templates |
| - | + | < | |
| - | ```yaml | + | |
| templates: | templates: | ||
| - " | - " | ||
| Line 38: | Line 36: | ||
| # - " | # - " | ||
| # - " | # - " | ||
| - | ``` | + | </ |
| - | + | ||
| - | #### Change Port Mapping | + | |
| - | ```yaml | + | === Change Port Mapping === |
| + | < | ||
| expose: | expose: | ||
| - " | - " | ||
| # - " | # - " | ||
| - | ``` | + | </ |
| **Rationale: | **Rationale: | ||
| - | ### 4. Run Discourse Setup | + | ==== 4. Run Discourse Setup ==== |
| - | ```bash | + | < |
| ./ | ./ | ||
| - | ``` | + | </ |
| **Note:** This process takes time. Good opportunity for a coffee break. | **Note:** This process takes time. Good opportunity for a coffee break. | ||
| Line 61: | Line 58: | ||
| Provide these values during setup: | Provide these values during setup: | ||
| - | - **Hostname: | + | * **Hostname: |
| - | - **Developer Email: | + | |
| - | - **SMTP Server:** Your SMTP server address | + | |
| - | - **SMTP Port:** Typically | + | |
| - | - **SMTP Username:** Your SMTP username | + | |
| - | - **SMTP Password:** Your SMTP password | + | |
| - | - **Notification Email:** Email address for outgoing notifications | + | |
| - | - **Let' | + | |
| - | ### 5. Copy Backup File to Server | + | ==== 5. Copy Backup File to Server |
| From your local machine (or source location), copy the backup file: | From your local machine (or source location), copy the backup file: | ||
| - | + | < | |
| - | ```bash | + | scp -P < |
| - | scp -P < | + | </ |
| - | ``` | + | |
| **Notes:** | **Notes:** | ||
| - | - Replace | + | * Replace |
| - | - Replace | + | |
| - | - Adjust username and hostname as needed for your setup | + | |
| - | ### 6. Restore Backup from Admin Panel | + | ==== 6. Restore Backup from Admin Panel ==== |
| - | 1. Access Discourse at `http://your-domain.com` (or server IP if DNS not yet configured) | + | - Access Discourse at '' |
| - | 2. Navigate to **Admin → Backups** | + | |
| - | 3. Locate the uploaded backup file in the list | + | |
| - | 4. Click **Restore** on the backup file | + | |
| - | 5. Confirm the restoration | + | |
| **Note:** The restoration process will restart Discourse and may take several minutes depending on backup size. | **Note:** The restoration process will restart Discourse and may take several minutes depending on backup size. | ||
| - | ### 7. Install and Configure Nginx | + | ==== 7. Install and Configure Nginx ==== |
| + | |||
| + | #This nginx setup in this guide is but one valid configuration. | ||
| + | <code bash> | ||
| + | apt-get install nginx | ||
| + | </ | ||
| + | |||
| + | ==== 8. Create Nginx Configuration (Example) ==== | ||
| + | |||
| + | **Note:** This configuration represents one working approach for proxying Discourse through nginx. Alternative configurations may work equally well depending on your specific requirements, | ||
| + | |||
| + | Create ''/ | ||
| + | <code nginx> | ||
| + | server { | ||
| + | listen 80; | ||
| + | server_name example.com; | ||
| + | |||
| + | location / { | ||
| + | proxy_pass http:// | ||
| + | proxy_set_header Host $host; | ||
| + | proxy_set_header X-Real-IP $remote_addr; | ||
| + | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
| + | proxy_set_header X-Forwarded-Proto $scheme; | ||
| + | proxy_set_header X-Forwarded-Host $host; | ||
| + | |||
| + | | ||
| + | proxy_http_version 1.1; | ||
| + | proxy_set_header Upgrade $http_upgrade; | ||
| + | proxy_set_header Connection " | ||
| + | |||
| + | | ||
| + | proxy_connect_timeout 60s; | ||
| + | proxy_send_timeout 60s; | ||
| + | proxy_read_timeout 60s; | ||
| + | |||
| + | # Buffer settings | ||
| + | proxy_buffering off; | ||
| + | proxy_redirect off; | ||
| + | } | ||
| + | |||
| + | client_max_body_size 10m; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | **Key Discourse Requirements: | ||
| + | * '' | ||
| + | * WebSocket support ('' | ||
| + | * Buffering disabled ('' | ||
| + | * File upload size limit ('' | ||
| + | |||
| + | ==== 9. Enable Nginx Configuration ==== | ||
| + | <code bash> | ||
| + | # Remove default site (prevents conflicts) | ||
| + | rm / | ||
| + | |||
| + | # Create symlink to enable site | ||
| + | ln -s / | ||
| + | |||
| + | # Test configuration | ||
| + | nginx -t | ||
| + | |||
| + | # Reload nginx | ||
| + | systemctl reload nginx | ||
| + | </ | ||
| + | |||
| + | ==== 10. Install | ||
| + | <code bash> | ||
| + | snap install --classic certbot | ||
| + | </ | ||
| + | |||
| + | ==== 11. Generate SSL Certificate ==== | ||
| + | <code bash> | ||
| + | certbot | ||
| + | </ | ||
| + | |||
| + | Certbot will run interactively | ||
| + | * Detect the nginx configuration for your domain | ||
| + | * Prompt for domain selection | ||
| + | * Automatically generate Let's Encrypt certificates | ||
| + | * Modify nginx configuration to enable HTTPS | ||
| + | * Configure | ||
| + | |||
| + | ==== 12. Reload | ||
| + | <code bash> | ||
| + | systemctl reload nginx | ||
| + | </ | ||
| + | |||
| + | Your Discourse instance should now be accessible at '' | ||
| + | |||
| + | ===== Important Notes ===== | ||
| + | |||
| + | ==== YAML Sensitivity ==== | ||
| + | * Be extremely careful with whitespace and alignment when editing '' | ||
| + | * Validate syntax at http:// | ||
| + | |||
| + | ==== Undocumented Discourse Behavior ==== | ||
| + | * The '' | ||
| + | * Entering '' | ||
| + | * Commenting out SSL templates is required but not obvious for reverse proxy scenarios | ||
| + | * Discourse' | ||
| + | |||
| + | ==== Security Considerations ==== | ||
| + | * Use strong, unique passwords for SMTP and admin accounts | ||
| + | * Secure transfer of backup files containing sensitive data | ||
| + | * Review and update SMTP credentials appropriately for your environment | ||
| + | * Consider firewall rules to restrict access to port 8080 | ||
| + | |||
| + | ===== Troubleshooting ===== | ||
| + | |||
| + | ==== Nginx Shows Default Welcome Page ==== | ||
| + | * Verify default site removed: '' | ||
| + | * Confirm symlink exists: '' | ||
| + | * Test config: '' | ||
| + | * Reload: '' | ||
| + | |||
| + | ==== SSL Certificate Permission Errors ==== | ||
| + | * Run nginx test with sudo: '' | ||
| + | * Check for other configs referencing non-existent certificates | ||
| + | * Ensure only HTTP (port 80) configuration exists before running certbot | ||
| + | |||
| + | ==== Backup Not Visible in Admin Panel ==== | ||
| + | * Verify file copied to correct path: ''/ | ||
| + | * Check file permissions: | ||
| + | * Ensure filename ends in '' | ||
| + | |||
| + | ==== SCP Syntax Issues ====https:// | ||
| + | * Port flag is capital '' | ||
| + | * Syntax: '' | ||
| + | * Example: '' | ||
| + | |||
| + | ``` | ||
| + | ===== Discourse API Configuration ===== | ||
| + | |||
| + | ==== Webhook Configuration ==== | ||
| + | |||
| + | === Available Events === | ||
| + | |||
| + | **Topic Events** | ||
| + | * Topic is created | ||
| + | * Topic is revised | ||
| + | * Topic is updated | ||
| + | * Topic is deleted | ||
| + | * Topic is recovered | ||
| + | |||
| + | **Post Events** | ||
| + | * Post is created | ||
| + | * Post is updated | ||
| + | * Post is deleted | ||
| + | * Post is recovered | ||
| + | |||
| + | **User Events** | ||
| + | * User logged in | ||
| + | * User logged out | ||
| + | * User confirmed e-mail | ||
| + | * User is created | ||
| + | * User is approved | ||
| + | * User is updated | ||
| + | * User is deleted | ||
| + | * User is suspended | ||
| + | * User is unsuspended | ||
| + | * User is anonymized | ||
| + | |||
| + | **Group Events** | ||
| + | * Group is created | ||
| + | * Group is updated | ||
| + | * Group is deleted | ||
| + | |||
| + | **Category Events** | ||
| + | * Category is created | ||
| + | * Category is updated | ||
| + | * Category is deleted | ||
| + | |||
| + | **Tag Events** | ||
| + | * Tag is created | ||
| + | * Tag is updated | ||
| + | * Tag is deleted | ||
| + | |||
| + | **Chat Events** (Critical for sync) | ||
| + | * Message is created | ||
| + | * Message is edited | ||
| + | * Message is trashed | ||
| + | * Message is restored | ||
| + | |||
| + | **Other Events** | ||
| + | * Reviewable Events | ||
| + | * Notification Events | ||
| + | * Solved Events | ||
| + | * Badge Events | ||
| + | * Group User Events | ||
| + | * Like Events | ||
| + | * User Promoted Events | ||
| + | * Topic Voting Events | ||
| + | |||
| + | === Webhook Settings === | ||
| + | |||
| + | **Content Type:** '' | ||
| + | |||
| + | **Secret:** Optional string for generating HMAC signatures (implement signature verification to prevent spoofed webhooks) | ||
| + | |||
| + | **Filtering Options: | ||
| + | * Triggered Categories - only fire webhooks for specific categories | ||
| + | * Triggered Tags - only fire webhooks for specific tags | ||
| + | * Triggered Groups - only fire webhooks for specific groups | ||
| + | * Leave blank to trigger for all | ||
| + | |||
| + | **TLS Certificate Check:** Enable unless using self-signed certs in development | ||
| + | |||
| + | === Configured Webhook Endpoints === | ||
| + | |||
| + | **hobbiesync webhook: | ||
| + | * URL: '' | ||
| + | |||
| + | **general webhook: | ||
| + | * URL: '' | ||
| + | |||
| + | Path structure: ''/ | ||
| + | |||
| + | ==== Chat API Endpoints ==== | ||
| + | |||
| + | Base path: ''/ | ||
| + | |||
| + | === Channel Operations === | ||
| + | |||
| + | **List channels** | ||
| + | < | ||
| + | GET / | ||
| + | </ | ||
| + | |||
| + | **Get specific channel** | ||
| + | < | ||
| + | GET / | ||
| + | </ | ||
| + | |||
| + | **List messages** | ||
| + | < | ||
| + | GET / | ||
| + | </ | ||
| + | |||
| + | **Send message** | ||
| + | < | ||
| + | POST / | ||
| + | </ | ||
| + | |||
| + | === Authentication === | ||
| + | |||
| + | Discourse API requires headers: | ||
| + | < | ||
| + | Api-Key: {your_api_key} | ||
| + | Api-Username: | ||
| + | </ | ||
| + | |||
| + | **API Credentials: | ||
| + | * API Key: '' | ||
| + | * Username: '' | ||
| + | |||
| + | **Security Notes:** | ||
| + | * Store credentials in environment variables or secrets management (Vault) | ||
| + | * API key has full access as the bot user | ||
| + | * Revoke/ | ||
| + | * Ensure bot user has appropriate permissions (chat access, posting rights to target channels) | ||
| + | |||
| + | ==== Architecture Considerations ==== | ||
| + | |||
| + | === Critical Events for Chat Sync === | ||
| + | |||
| + | **Primary focus:** | ||
| + | * Chat events (message created/ | ||
| + | * Post events - if bridging forum posts to Discord channels | ||
| + | * User events (login/ | ||
| + | |||
| + | === Bidirectional Sync Challenges === | ||
| + | |||
| + | **Discourse → Discord: | ||
| + | * Webhooks handle this direction | ||
| + | * Parse incoming webhook payloads | ||
| + | * Map to Discord API calls | ||
| + | |||
| + | **Discord → Discourse: | ||
| + | * Discord bot with message event listeners | ||
| + | * POST to Discourse API ''/ | ||
| + | * Requires Discord bot token and event subscriptions | ||
| + | |||
| + | **Loop Prevention: | ||
| + | * Track message IDs/sources to avoid infinite echo | ||
| + | * Store mapping of Discourse message ID ↔ Discord message ID | ||
| + | * Ignore messages from own bot user | ||
| + | |||
| + | === State Management === | ||
| + | |||
| + | Required mappings to store: | ||
| + | * Message ID mapping (Discourse chat message ID ↔ Discord message ID) | ||
| + | * User mapping (Discourse username ↔ Discord user/ | ||
| + | * Channel mapping (Discourse channel ID ↔ Discord channel ID) | ||
| + | * Edit/delete operations require retrieving these mappings | ||
| + | |||
| + | === Event Filtering Strategy === | ||
| + | |||
| + | **Options: | ||
| + | * "Send me everything" | ||
| + | * Selective events - choose specific event types | ||
| + | * Category/ | ||
| + | |||
| + | **Recommendation: | ||
| + | |||
| + | === Implementation Flow === | ||
| + | |||
| + | **Webhook Receiver (Discourse → Discord): | ||
| + | - Receive webhook POST request | ||
| + | - Verify HMAC signature if secret configured | ||
| + | - Parse JSON payload | ||
| + | - Extract message/ | ||
| + | - Check for existing mapping (to detect edits/ | ||
| + | - Call Discord API | ||
| + | - Store message ID mapping | ||
| + | |||
| + | **Discord Bot (Discord → Discourse): | ||
| + | - Listen for Discord message events | ||
| + | - Check if message is from bot (skip to prevent loop) | ||
| + | - Format message for Discourse | ||
| + | - POST to Discourse API with credentials | ||
| + | - Store message ID mapping | ||
| + | |||
| + | **Message History/ | ||
| + | * Use '' | ||
| + | * Implement pagination if needed | ||
| + | * Consider rate limiting | ||
| + | |||
| + | |||
| + | ===== Profile Image/ | ||
| + | |||
| + | ==== How Avatar Syncing Works ==== | ||
| + | |||
| + | The bridge synchronizes profile images/ | ||
| + | |||
| + | === Discourse → Discord Avatar Flow === | ||
| + | |||
| + | - Bridge polls Discourse Chat API every 1.5 seconds for new messages | ||
| + | - Each message includes user info with an '' | ||
| + | - Avatar template format: ''/ | ||
| + | - Bridge calls '' | ||
| + | * Replaces '' | ||
| + | * Prepends Discourse base URL if path is relative | ||
| + | - Full avatar URL passed to Discord webhook '' | ||
| + | - Discord displays the message with the Discourse user's actual avatar | ||
| + | |||
| + | <code python> | ||
| + | # Avatar URL construction (bridge.py lines 160-186) | ||
| + | def build_avatar_url(self, | ||
| + | if not avatar_template: | ||
| + | return None | ||
| + | avatar_path = avatar_template.replace(' | ||
| + | if not avatar_path.startswith('/' | ||
| + | avatar_path = '/' | ||
| + | return f" | ||
| + | </ | ||
| + | |||
| + | === Discord → Discourse Avatar Flow === | ||
| + | |||
| + | Discord user avatars are **not** synced to Discourse. Messages from Discord appear with the bot's configured username in Discourse, prefixed with '' | ||
| + | |||
| + | === User Impersonation === | ||
| + | |||
| + | The bridge uses Discord webhooks to display messages from Discourse users with their actual: | ||
| + | * **Username** - Discourse display name shown in Discord | ||
| + | * **Avatar** - Discourse profile image displayed in Discord | ||
| + | |||
| + | This creates a seamless experience where Discourse users appear as distinct users in Discord channels. | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Image Upload/ | ||
| + | |||
| + | ==== Discord → Discourse Images ==== | ||
| + | |||
| + | - Discord attachments detected via '' | ||
| + | - Images downloaded to temporary files using '' | ||
| + | - Uploaded to Discourse via ''/ | ||
| + | - Upload URL appended to message as markdown: '' | ||
| + | - Temporary files cleaned up after processing | ||
| + | |||
| + | ==== Discourse → Discord Images ==== | ||
| + | |||
| + | - Images extracted from '' | ||
| + | - Relative URLs converted to absolute URLs (prepends base_url) | ||
| + | - URLs appended to message content | ||
| + | - Discord auto-embeds images from URLs | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== GIF and Video Handling ===== | ||
| + | |||
| + | ==== Giphy/Tenor URL Detection ==== | ||
| + | |||
| + | The bridge detects and converts Giphy/Tenor share URLs to direct media URLs for proper embedding: | ||
| + | |||
| + | * **Giphy**: '' | ||
| + | * **Tenor**: '' | ||
| + | |||
| + | ==== MP4 to GIF Conversion ==== | ||
| + | |||
| + | When Giphy/Tenor returns MP4 URLs (common for larger animations), | ||
| + | |||
| + | - Downloads MP4 to temporary file | ||
| + | - Generates optimized color palette for compression | ||
| + | - Converts to GIF with configurable settings: | ||
| + | * Frame rate (default: 15 FPS) | ||
| + | * Width scaling (default: 480px) | ||
| + | * Duration limit (default: 10 seconds) | ||
| + | * CPU thread limit (default: 3 threads) | ||
| + | - Uploads converted GIF to Discourse | ||
| + | - Cleans up temporary files | ||
| + | |||
| + | FFmpeg configuration options in '' | ||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== State Management ===== | ||
| + | |||
| + | ==== Message Tracking ==== | ||
| + | |||
| + | The bridge tracks processed message IDs to prevent duplicates and enable resumption after restarts. | ||
| + | |||
| + | State file structure ('' | ||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== State Initialization ==== | ||
| + | |||
| + | On first run, the bridge initializes state with the **latest** message IDs to prevent flooding channels with historical messages. | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Loop Prevention ===== | ||
| + | |||
| + | The bridge implements multiple safeguards to prevent infinite message loops: | ||
| + | |||
| + | ==== Discord → Discourse ==== | ||
| + | * Messages prefixed with '' | ||
| + | * Webhook messages (non-user) are detected and skipped | ||
| + | * Bot's own messages filtered by user ID | ||
| + | |||
| + | ==== Discourse → Discord ==== | ||
| + | * Messages containing '' | ||
| + | * Prevents echoing messages that originated from Discord | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Configuration Reference ===== | ||
| + | |||
| + | ==== Full Configuration Structure ==== | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | ] | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Configuration Options ==== | ||
| + | |||
| + | ^ Option ^ Default ^ Description ^ | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | |||
| + | ==== Environment Variable Overrides ==== | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Channel Mapping ===== | ||
| + | |||
| + | ==== Multi-Channel Support ==== | ||
| + | |||
| + | The bridge supports multiple independent channel pairs. Each pair has: | ||
| + | * Discord channel ID | ||
| + | * Discourse channel ID | ||
| + | * Separate webhook URLs | ||
| + | * Independent enable/ | ||
| + | * Separate state tracking | ||
| + | |||
| + | ==== Adding a New Channel Pair ==== | ||
| + | |||
| + | - Add entry to '' | ||
| + | - Set '' | ||
| + | - Create Discourse webhook and add URL to '' | ||
| + | - Set '' | ||
| + | - Restart bridge - Discord webhook created automatically | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Error Handling and Retry Logic ===== | ||
| + | |||
| + | ==== Automatic Retry ==== | ||
| + | |||
| + | * Failed Discord webhook sends **do not** update state | ||
| + | * Message remains in queue and retries on next poll cycle | ||
| + | * Prevents message loss on temporary failures | ||
| + | |||
| + | ==== Graceful Degradation ==== | ||
| + | |||
| + | * API errors caught and logged, processing continues | ||
| + | * Image download failures don't block message delivery | ||
| + | * FFmpeg conversion failures fall back to posting MP4 URL | ||
| + | |||
| + | ==== Timeouts ==== | ||
| + | |||
| + | * API requests: 30-60 second timeout | ||
| + | * FFmpeg conversion: 30-60 second timeout | ||
| + | * Prevents indefinite hangs | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Docker Deployment ===== | ||
| + | |||
| + | ==== Container Configuration ==== | ||
| + | |||
| + | <code yaml> | ||
| + | services: | ||
| + | syncbot: | ||
| + | build: . | ||
| + | restart: unless-stopped | ||
| + | deploy: | ||
| + | resources: | ||
| + | limits: | ||
| + | cpus: ' | ||
| + | memory: 512M | ||
| + | logging: | ||
| + | driver: " | ||
| + | options: | ||
| + | max-size: " | ||
| + | max-file: " | ||
| + | volumes: | ||
| + | - ./ | ||
| + | - ./ | ||
| + | </ | ||
| + | |||
| + | ==== Security ==== | ||
| + | |||
| + | * Runs as non-root user inside container | ||
| + | * Config mounted read-only | ||
| + | * State file writable for persistence | ||
| + | * Resource limits prevent runaway processes | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Message Flow Diagrams ===== | ||
| + | |||
| + | ==== Discord → Discourse ==== | ||
| + | |||
| + | < | ||
| + | Discord User posts message | ||
| + | ↓ | ||
| + | on_message() event triggered | ||
| + | ↓ | ||
| + | Check: Is webhook message? → Skip | ||
| + | Check: Is bot message? → Skip | ||
| + | ↓ | ||
| + | Find channel config by Discord channel ID | ||
| + | ↓ | ||
| + | Download image attachments (if any) | ||
| + | ↓ | ||
| + | Upload images to Discourse | ||
| + | ↓ | ||
| + | Extract/ | ||
| + | ↓ | ||
| + | Convert MP4 to GIF (if needed) | ||
| + | ↓ | ||
| + | Format: " | ||
| + | ↓ | ||
| + | Append image URLs as markdown | ||
| + | ↓ | ||
| + | Send via Discourse webhook | ||
| + | ↓ | ||
| + | Update Discord channel state | ||
| + | </ | ||
| + | |||
| + | ==== Discourse → Discord ==== | ||
| + | |||
| + | < | ||
| + | poll_discourse() runs every 1.5 seconds | ||
| + | ↓ | ||
| + | For each enabled channel: | ||
| + | ↓ | ||
| + | GET messages after last_message_id | ||
| + | ↓ | ||
| + | For each new message: | ||
| + | ↓ | ||
| + | Check: Has [Discord] prefix? → Skip | ||
| + | ↓ | ||
| + | Extract username from user info | ||
| + | Extract avatar_template from user info | ||
| + | ↓ | ||
| + | Build avatar URL (replace {size}, add base_url) | ||
| + | ↓ | ||
| + | Extract image uploads, make URLs absolute | ||
| + | ↓ | ||
| + | Truncate content to 2000 chars if needed | ||
| + | ↓ | ||
| + | Send via Discord webhook: | ||
| + | - username: Discourse username | ||
| + | - avatar_url: Discourse avatar | ||
| + | - content: message text | ||
| + | ↓ | ||
| + | Success? → Update Discourse channel state | ||
| + | Failed? → Retry on next poll | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Dependencies ===== | ||
| + | |||
| + | ==== Python Packages ==== | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | ==== System Dependencies ==== | ||
| + | |||
| + | * Python 3.11+ | ||
| + | * FFmpeg (for MP4 to GIF conversion) | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Known Limitations ===== | ||
| + | |||
| + | ==== Not Currently Implemented ==== | ||
| + | |||
| + | * **Message Edit Sync** - Edits are not synchronized | ||
| + | * **Message Delete Sync** - Deletions are not synchronized | ||
| + | * **Reaction Sync** - Emoji reactions not bridged | ||
| + | * **PluralKit Support** - Removed in current version (was in earlier versions per git history) | ||
| + | * **Discord Avatar → Discourse** - Only Discourse avatars sync to Discord, not vice versa | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Troubleshooting ===== | ||
| + | |||
| + | ==== Messages Not Syncing ==== | ||
| + | |||
| + | * Check '' | ||
| + | * Verify Discord bot has webhook permissions in channel | ||
| + | * Check Discourse webhook URL is correct | ||
| + | * Review logs for API errors | ||
| + | |||
| + | ==== Duplicate Messages ==== | ||
| + | |||
| + | * Check state file permissions (must be writable) | ||
| + | * Verify state file path matches config | ||
| + | |||
| + | ==== Images Not Appearing ==== | ||
| + | |||
| + | * Verify Discourse API key has upload permissions | ||
| + | * Check temporary directory is writable | ||
| + | * Review logs for upload errors | ||
| + | |||
| + | ==== GIFs Not Converting ==== | ||
| + | |||
| + | * Ensure FFmpeg is installed: '' | ||
| + | * Check FFmpeg is in PATH | ||
| + | * Review logs for conversion errors | ||
| + | * Try reducing '' | ||
| - | ```bash | ||
| - | apt | ||
discourse.1769148065.txt.gz · Last modified: by 208.92.105.181
