{"id":18425777,"url":"https://github.com/faforever/faf-qai","last_synced_at":"2026-01-27T13:01:26.754Z","repository":{"id":37905177,"uuid":"309677322","full_name":"FAForever/faf-qai","owner":"FAForever","description":"QAI reloaded","archived":false,"fork":false,"pushed_at":"2026-01-12T17:52:39.000Z","size":985,"stargazers_count":2,"open_issues_count":5,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-12T21:57:05.980Z","etag":null,"topics":["backend","bot","discord","faforever","irc","qai"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FAForever.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"patreon":"faf"}},"created_at":"2020-11-03T12:19:42.000Z","updated_at":"2026-01-12T17:52:44.000Z","dependencies_parsed_at":"2024-07-28T10:50:40.821Z","dependency_job_id":"c428bab8-cbae-4b6c-8ea7-ff373c03a43d","html_url":"https://github.com/FAForever/faf-qai","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/FAForever/faf-qai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FAForever%2Ffaf-qai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FAForever%2Ffaf-qai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FAForever%2Ffaf-qai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FAForever%2Ffaf-qai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FAForever","download_url":"https://codeload.github.com/FAForever/faf-qai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FAForever%2Ffaf-qai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28813223,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T12:25:15.069Z","status":"ssl_error","status_checked_at":"2026-01-27T12:25:05.297Z","response_time":168,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","bot","discord","faforever","irc","qai"],"created_at":"2024-11-06T05:05:34.487Z","updated_at":"2026-01-27T13:01:26.748Z","avatar_url":"https://github.com/FAForever.png","language":"C#","funding_links":["https://patreon.com/faf"],"categories":[],"sub_categories":[],"readme":"![Continuous integration](https://github.com/FAForever/faf-qai/workflows/Continuous%20integration/badge.svg)\n\n# Faforever.Qai\n\nA unified Discord and IRC bot for the Forged Alliance Forever (FAF) community. This project combines and rewrites the functionality of both Dostya and QAI into a single, modern application.\n\n## Overview\n\nFaforever.Qai serves as the official community bot for FAF, providing:\n- **Discord Integration**: Rich Discord bot with slash commands, role management, and account linking\n- **IRC Support**: Full IRC client support for traditional FAF channels\n- **Dual Platform Commands**: Unified command system working across both Discord and IRC\n- **FAF API Integration**: Direct integration with FAF services for player stats, replays, maps, and more\n- **Account Linking**: Secure OAuth2-based system to link Discord accounts with FAF accounts\n\n## Project History\n\n### QAI Legacy\nThis is a total rewrite of [QAI](https://github.com/FAForever/QAI) - the original IRC bot that served the FAF community for years.\n\n### Dostya Legacy  \nThis incorporates and rewrites [Dostya](https://github.com/FAForever/Dostya) - the Discord bot that provided modern Discord integration.\n\n## Key Features\n\n### Multi-Platform Support\n- **Discord**: Modern slash commands, embeds, role management\n- **IRC**: Traditional text commands, channel management\n- **Unified Codebase**: Single application handling both platforms\n\n### FAF Integration\n- Player statistics and rankings\n- Replay fetching and analysis\n- Map information and ladder pools\n- Live Twitch stream monitoring\n- Clan information lookup\n- Unit database searches\n\n### Account Management\n- Secure Discord ↔ FAF account linking via OAuth2\n- Automatic role assignment for verified users\n- Staff tools for account management\n- Guild-specific configuration options\n\n### Command Categories\n- **Player Commands**: `!player`, `!seen`, statistics\n- **Game Commands**: `!replay`, `!map`, `!unit`\n- **Fun Commands**: `!8ball`, `!roll`, `!taunt`, `!hug`\n- **Utility Commands**: `!help`, `!alive`, `!patch`\n- **Admin Commands**: Link management, moderation tools\n\n## Installation\n\n### Quick Start\n1. Download the latest release\n2. Extract to desired folder\n3. Run `Qai.exe` executable\n4. Configure using environment variables or config files\n\n### Development Setup\n```bash\n# Clone the repository\ngit clone https://github.com/FAForever/faf-qai.git\ncd faf-qai\n\n# Restore dependencies\ndotnet restore\n\n# Create database\ndotnet ef database update --project src/Faforever.Qai.Core\n\n# Run the application\ndotnet run --project src/Faforever.Qai\n```\n\n## Configuration\n\n### Understanding Configuration Variables\n\nThe bot uses a dual configuration system that supports both modern hierarchical configuration and legacy environment variables:\n\n#### Configuration Loading Order\n1. **appsettings.json** - Default values and structure\n2. **Environment Variables** - Override defaults (two formats supported)\n3. **Legacy Environment Variables** - Backward compatibility\n\n#### Variable Naming Formats\n\n**Format 1: Hierarchical Configuration (Recommended)**\nUses double underscores (`__`) to represent JSON hierarchy:\n```bash\nConfig__Discord__ClientId=your_discord_client_id\nConfig__Faf__ClientId=your_faf_client_id\nConfig__Host=yourdomain.com\n```\nThis maps to the JSON structure in `appsettings.json`:\n```json\n{\n  \"Config\": {\n    \"Discord\": {\n      \"ClientId\": \"your_discord_client_id\"\n    },\n    \"Faf\": {\n      \"ClientId\": \"your_faf_client_id\"\n    },\n    \"Host\": \"yourdomain.com\"\n  }\n}\n```\n\n**Format 2: Legacy Environment Variables**\nSimple uppercase names for backward compatibility:\n```bash\nDISCORD_TOKEN=your_discord_bot_token\nDISCORD_CLIENT_SECRET=your_discord_client_secret\nFAF_CLIENT_SECRET=your_faf_client_secret\n```\n\n#### Why Two Formats?\n\n- **Hierarchical (`Config__*`)**: Maps directly to the configuration structure, more explicit and clear\n- **Legacy (`*_SECRET`)**: Shorter names, commonly used for sensitive data in container environments\n\n**Best Practice**: Use hierarchical format for clarity, except for secrets where legacy format is more common in container deployments.\n\n### Docker Compose Configuration\n\nExample `docker-compose.yml` with proper environment configuration:\n\n```yaml\nversion: '3.8'\nservices:\n  qai-bot:\n    image: ghcr.io/faforever/faf-qai:latest\n    container_name: faf-qai\n    environment:\n      # Required: Discord Bot Token (legacy format)\n      - DISCORD_TOKEN=your_discord_bot_token_here\n      \n      # Required: Discord OAuth2 Credentials (for account linking)\n      - Config__Discord__ClientId=your_discord_client_id\n      - DISCORD_CLIENT_SECRET=your_discord_client_secret  # Legacy format for secrets\n      \n      # Required: FAF OAuth2 Credentials (for account linking)  \n      - Config__Faf__ClientId=your_faf_client_id\n      - FAF_CLIENT_SECRET=your_faf_client_secret  # Legacy format for secrets\n      \n      # Required: Host Configuration (your public domain for OAuth2)\n      - Config__Host=yourdomain.com\n      \n      # Optional: IRC Configuration\n      - Config__Irc__Connection=irc.faforever.com\n      - Config__Irc__User=your_bot_name\n      - Config__Irc__Channels=aeolus,newbie\n      \n      # Optional: API Endpoints (defaults shown)\n      - Config__Faf__Api=https://api.faforever.com\n      - Config__Discord__Api=https://discord.com/api\n      \n      # Optional: Twitch Integration\n      - Config__Twitch__ClientId=your_twitch_client_id\n      - TWITCH_CLIENT_SECRET=your_twitch_client_secret\n      \n      # Optional: Logging Level\n      - Logging__LogLevel__Default=Information\n      \n    ports:\n      - \"5000:5000\"  # Required for OAuth2 callbacks\n    volumes:\n      - ./data:/app/data  # Persist database\n    restart: unless-stopped\n```\n\n### Environment Variables Reference\n\n| Environment Variable Name       | Required        | Description                                              |\n|---------------------------------|-----------------|----------------------------------------------------------|\n| `DISCORD_TOKEN`                 | Required        | Discord bot token                                        |\n| `Config__Discord__ClientId`     | Account Linking | Discord OAuth2 client ID (for account linking)           |\n| `DISCORD_CLIENT_SECRET`         | Account Linking | Discord OAuth2 client secret (for account linking)       |\n| `Config__Faf__ClientId`         | Account Linking | FAF OAuth2 client ID (for account linking)               |\n| `FAF_CLIENT_SECRET`             | Account Linking | FAF OAuth2 client secret (for account linking)           |\n| `Config__Host`                  | Account Linking | Public domain for OAuth2 callbacks                       |\n| `Config__Irc__Connection`       | Optional        | IRC server (default: `irc.faforever.com`)                |\n| `Config__Irc__User`             | Optional        | IRC bot username                                         |\n| `Config__Irc__Channels`         | Optional        | IRC channels to join (default: `aeolus,newbie`)          |\n| `Config__Faf__Api`              | Optional        | FAF API base URL (default: `https://api.faforever.com`)  |\n\n**Legend:**\n- **Required** - Essential for basic bot functionality\n- **Account Linking** - Required only for Discord ↔ FAF account linking feature\n- **Optional** - Has sensible defaults, override only if needed\n\n### Database Setup\nCreate a new database for testing:\n```bash\n# Using Package Manager Console\nUpdate-Database --project Faforever.Qai.Core\n\n# Using .NET Core CLI\ndotnet ef database update --project src/Faforever.Qai.Core\n```\n\nEnsure the `test.db` file is in the `Faforever.Qai` project with `Copy to Output Directory` set to \"Copy if newer\".\n\n### EF Core Tools\nInstall Entity Framework tools for database management:\n```bash\ndotnet tool install --global dotnet-ef\n```\n\nReference: [EF Core Tools Documentation](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet)\n\n## Usage\n\n### Discord Commands\nUse slash commands in Discord:\n- `/player \u003cusername\u003e` - Get player statistics\n- `/link` - Link your Discord account to FAF\n- `/replay \u003cid\u003e` - Get replay information\n- `/map \u003cname\u003e` - Search for maps\n\n### IRC Commands  \nUse traditional prefix commands in IRC channels:\n- `!player coolmcgrrr` - Get player statistics\n- `!replay 12345` - Get replay information\n- `!alive` - Check bot status\n\nAll commands are prefixed with `!` in IRC and #aeolus channels.\n\n## Architecture\n\n### Project Structure\n```\nsrc/\n├── Faforever.Qai/              # Main application and API\n├── Faforever.Qai.Core/         # Core business logic and commands\n├── Faforever.Qai.Discord/      # Discord-specific implementations\n├── Faforever.Qai.Irc/         # IRC client implementation\n└── IrcDotNet/                  # IRC protocol library\n```\n\n### Key Components\n- **Command System**: Unified command processing for both platforms\n- **API Integration**: HTTP clients for FAF services\n- **OAuth2 Flow**: Secure account linking system\n- **Database Layer**: Entity Framework Core with SQLite\n- **Service Layer**: Business logic and operations\n\n## FAF Discord Account Linking Setup\n\n### Overview\n\nThe account linking feature allows Discord users to securely link their Discord accounts with their FAF accounts using OAuth2 authentication. This enables automatic role assignment, verification of FAF membership, and access to FAF-specific commands.\n\n### Prerequisites\n\nBefore setting up account linking, you need:\n\n1. **Discord Application**: A Discord bot application with OAuth2 credentials\n2. **FAF OAuth2 Application**: Client credentials for FAF API access\n3. **Public Domain/IP**: A publicly accessible domain or IP for OAuth2 callbacks\n4. **SSL Certificate**: HTTPS is required for OAuth2 (recommended: Let's Encrypt)\n\n### Setup Steps\n\n#### 1. Discord Application Setup\n\n1. Go to [Discord Developer Portal](https://discord.com/developers/applications)\n2. Create a new application or select existing one\n3. Navigate to **OAuth2** → **General**\n4. Note down your **Client ID** and **Client Secret**\n5. Add redirect URI: `https://yourdomain.com/authorization-code/discord-callback`\n\n#### 2. FAF OAuth2 Application Setup\n\n1. Contact FAF administrators to create an OAuth2 application\n2. Provide your callback URL: `https://yourdomain.com/authorization-code/callback`\n3. Receive your **FAF Client ID** and **FAF Client Secret**\n4. Note the FAF API base URL (usually `https://api.faforever.com/`)\n\n#### 3. Reverse Proxy Setup (Nginx Example)\n\nConfigure your reverse proxy to handle HTTPS and forward requests to the bot:\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name yourdomain.com;\n    \n    ssl_certificate /path/to/your/certificate.pem;\n    ssl_certificate_key /path/to/your/private.key;\n    \n    location /authorization-code/ {\n        proxy_pass http://localhost:5000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n    \n    location /api/link/ {\n        proxy_pass http://localhost:5000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\n### Required OAuth2 Endpoints\n\nThe bot exposes these endpoints that must be publicly accessible:\n\n| Endpoint                              | Purpose                             |\n|---------------------------------------|-------------------------------------|\n| `/authorization-code/discord-callback`| Discord OAuth2 callback             |\n| `/authorization-code/callback`        | FAF OAuth2 callback                 |\n| `/api/link/token/{token}`             | Link initiation endpoint            |\n| `/api/link/login`                     | Discord authentication start        |\n| `/api/link/auth`                      | FAF authentication start            |\n| `/api/link/denied`                    | OAuth2 access denial handler        |\n\n### Testing Account Linking\n\n1. **Start the bot**: `docker-compose up -d`\n2. **Test OAuth2 endpoints**: Visit `https://yourdomain.com/api/link/denied` (should return \"User denied account linking.\")\n3. **Test Discord command**: Use `/link` command in Discord\n\n### Usage Instructions\n\n#### Setting Up Role Assignment\n\n1. **Configure Link Role** (requires Manage Roles permission):\n   ```\n   /linkrole @VerifiedMember\n   ```\n   This role will be automatically assigned to users who complete the linking process.\n\n2. **Remove Link Role** (to disable automatic role assignment):\n   ```\n   /linkrole\n   ```\n\n#### User Linking Process\n\n1. User runs `/link` in Discord\n2. Bot sends a private message with a unique link\n3. User clicks the link and is redirected through OAuth2 flow:\n   - Discord authentication (verifies identity)\n   - FAF authentication (retrieves FAF account info)\n4. Upon completion, accounts are linked and role is assigned (if configured)\n\n#### Staff Management Commands\n\n| Command           | Permission    | Description                     |\n|-------------------|---------------|---------------------------------|\n| `/links \u003cuser\u003e`   | FAF Staff     | View link details for a user    |\n| `/unlink \u003cuser\u003e`  | FAF Staff     | Force remove account link       |\n| `/linkrole \u003crole\u003e`| Manage Roles  | Set role for linked users       |\n\n### Account Linking Troubleshooting\n\n#### Common Issues\n\n1. **\"No token found\" errors**\n   - Check that `Config__Host` matches your public domain\n   - Verify reverse proxy is forwarding requests correctly\n   - Ensure HTTPS is properly configured\n\n2. **OAuth2 callback errors**\n   - Verify callback URLs in Discord/FAF applications match your setup\n   - Check that port 5000 is properly exposed and forwarded\n   - Confirm SSL certificates are valid and not expired\n\n3. **Discord/FAF authentication fails**\n   - Verify client IDs and secrets are correct\n   - Check that Discord application has correct redirect URIs\n   - Ensure FAF application is approved and active\n\n4. **30-second timeout issues**\n   - Users must complete OAuth2 flow within 30 seconds\n   - Consider documenting this limitation for users\n   - Have users retry `/link` if they encounter timeouts\n\n#### Security Considerations\n\n- **HTTPS Required**: OAuth2 flows require HTTPS in production\n- **Secret Management**: Store secrets securely, never in public repositories\n- **Token Expiration**: 30-second tokens prevent replay attacks\n- **Scope Limitation**: OAuth2 scopes are minimal (Discord: \"identify\", FAF: \"public_profile\")\n- **Duplicate Prevention**: System prevents linking already-linked accounts\n\n## Documentation\n\n- **Command Reference**: See [Issue #9](https://github.com/FAForever/faf-qai/issues/9) for full command list\n- **API Documentation**: Integration details for FAF services\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests if applicable\n5. Submit a pull request\n\n## Troubleshooting\n\n### Common Issues\n\n1. **Bot not responding to commands**\n   - Check that `DISCORD_TOKEN` is set correctly\n   - Verify bot has proper permissions in Discord server\n   - Check Docker logs: `docker-compose logs qai-bot`\n\n2. **Database errors**\n   - Ensure database volume is properly mounted\n   - Check that database migrations have run\n   - Verify write permissions for data directory\n\n3. **IRC connection issues**\n   - Check `Config__Irc__Connection` points to correct server\n   - Verify IRC credentials if authentication required\n   - Check firewall/network connectivity\n\n### Debug Mode\n\nEnable debug logging for troubleshooting:\n```yaml\nenvironment:\n  - Logging__LogLevel__Default=Debug\n```\n\n### Health Checks\n\nMonitor these indicators for a healthy bot:\n- Bot responds to `/alive` command in Discord\n- IRC connection shows as active in logs\n- Database queries execute successfully\n- OAuth2 endpoints return appropriate responses (if linking enabled)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaforever%2Ffaf-qai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaforever%2Ffaf-qai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaforever%2Ffaf-qai/lists"}