Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 71 additions & 109 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,153 +2,115 @@

A Discord bot that automatically tracks CS2 players and roasts them based on their match statistics.

## Add to Your Server
## Installation Links

**[Add CS2 Roast Bot to Your Discord Server](https://discord.com/oauth2/authorize?scope=bot+applications.commands&client_id=1430077771920441387)**
**[Add to Server](https://discord.com/oauth2/authorize?scope=bot+applications.commands&client_id=1430077771920441387)** - Enable automatic roasting in your server

**[Install to Account](https://discord.com/oauth2/authorize?scope=applications.commands&client_id=1430077771920441387&integration_type=1)** - Use commands in DMs and anywhere

## Features

- **Slash Commands**: Modern Discord UI with autocomplete and inline help
- **Automatic Command Deployment**: Commands automatically register per server
- **Multi-Server Support**: Works across unlimited Discord servers
- **Auto-Onboarding**: Automatically sets up new servers with welcome messages
- **Per-Server Configuration**: Each server sets its own roast channel
- **Fully Automated Match Tracking**: Checks all linked players every hour
- **Cross-Server User Support**: Users can link in multiple servers
- **Smart API Caching**: Fetches Leetify data once, sends to all relevant servers
- **Stat Comparison**: Compares performance between matches
- **Performance-Based Roasts**: Roasts based on stat degradation
- **Intelligent Cooldown**: Only applies when no new matches detected (allows consecutive games)
- **Offline Guild Detection**: Detects servers joined while bot was offline
- **Auto-Cleanup**: Removes all data when bot leaves a server
- **State Persistence**: Restores previous state on startup
- **Configurable Timers**: Adjust check intervals via environment variables

## Installation

1. Install dependencies
```bash
npm install
```
- Automatic match tracking and roasting
- Works in servers, DMs, and group DMs
- Global and per-server account linking
- Slash commands with autocomplete
- Multi-server support with independent configuration
- Smart API caching and cooldown system
- Automatic onboarding for new servers

2. Create `.env` file
## Quick Start

3. Run the bot
### For Servers

## Initial Setup (Server Admins)
1. Add the bot to your server
2. Run `/setup channel #roasts` to configure the roast channel
3. Run `/link steam64_id:YOUR_ID` to link your Steam account
4. Get roasted automatically after matches

### 1. Add the Bot
When you add the bot to your server, it will:
- Automatically deploy slash commands
- Send a welcome message with setup instructions
- Be ready to use immediately
### For Personal Use

### 2. Configure the Bot
An admin must set up the roast channel using:
```
/setup channel #roasts
```

This sets where automatic roasts will be posted.
1. Install the bot to your account
2. Run `/link steam64_id:YOUR_ID` in DMs
3. Use `/stats`, `/roast`, and `/tracker check` anywhere

### 3. Check Setup Status
```
/setup status
```
Find your Steam64 ID at [steamid.io](https://steamid.io/)

## Commands

All commands are **slash commands** - start typing `/` in Discord to see them with autocomplete!
### Available Everywhere

### User Commands
- `/link steam64_id:YOUR_ID` - Link your Steam account
- `/stats` - View your stats
- `/stats user:@username` - View someone else's stats
- `/roast` - Roast yourself
- `/roast user:@username` - Roast someone else
- `/tracker check` - Manually check for new matches

**Link your Steam account:**
```
/link steam64_id:76561198123456789
```
Find your Steam64 ID at [steamid.io](https://steamid.io/)
### Server Only

**View your stats:**
```
/stats
/stats user:@username
```
- `/setup channel #channel` - Configure roast channel (Admin)
- `/setup status` - View server configuration
- `/tracker status` - View tracking status
- `/link steam64_id:ID user:@username` - Link another user (Admin)

**Check tracker status:**
```
/tracker status
/tracker check
/tracker check user:@username
```
## How It Works

### Admin Commands
### Automatic Roasting

**Setup roast channel (requires Manage Server permission):**
```
/setup channel #roasts
/setup status
```
- Bot checks for new matches every hour
- When a match is detected, stats are fetched and analyzed
- A roast is generated based on performance
- The roast is posted in configured server channels

**Link other users (requires Administrator permission):**
```
/link steam64_id:76561198123456789 user:@username
```
### Cooldown System

## Multi-Server Behavior
- No cooldown when matches are detected
- 3-hour cooldown when no new matches found
- Prevents API spam while allowing consecutive games

- Users can link their account in multiple servers
- Each server has its own configured roast channel
- When a user finishes a match, the **same roast** is sent to all servers where they're linked
- API is called only **once per user**, not once per server (efficient!)
- When bot leaves a server, all data for that server is automatically deleted
- Users linked in other servers remain unaffected
- Commands are automatically deployed per server when bot joins
- If bot was offline when added to a server, it detects and deploys commands on startup
### Multi-Server Support

## Cooldown System
- Link once, roast everywhere
- Each server has independent configuration
- API called once per user, roast sent to all servers
- Data automatically cleaned up when bot leaves

The bot uses an intelligent cooldown system:
- **Match detected**: No cooldown applied (allows players to play consecutive games)
- **No match detected**: 3-hour cooldown applied (prevents API spam for inactive players)
## Development Setup

This means players can play multiple CS2 games in a row and get roasted after each one!
### Requirements

## Configuration
- Node.js v16.9.0+
- Discord bot token
- Leetify API key from [api-public-docs.cs-prod.leetify.com](https://api-public-docs.cs-prod.leetify.com)

Create a `.env` file with the following variables:
### Installation

```bash
# Discord Bot Configuration
DISCORD_TOKEN=your_discord_bot_token_here
CLIENT_ID=your_discord_client_id_here

# Leetify API Configuration
LEETIFY_API_KEY=your_leetify_api_key_here
LEETIFY_API_BASE_URL=https://api-public.cs-prod.leetify.com

# Match Tracker Settings
CHECK_INTERVAL_MINUTES=60 # How often to check for new matches (default: 60 minutes)
USER_COOLDOWN_HOURS=3 # Cooldown period after no new match detected (default: 3 hours)
npm install
```

You can adjust the timing by changing the environment variables in `.env`.
### Configuration

## Requirements
Create a `.env` file:

- Node.js v16.9.0+
- Discord bot token
- Leetify API key
```bash
DISCORD_TOKEN=your_discord_bot_token
CLIENT_ID=your_discord_client_id
LEETIFY_API_KEY=your_leetify_api_key
LEETIFY_API_BASE_URL=https://api-public.cs-prod.leetify.com
CHECK_INTERVAL_MINUTES=60
USER_COOLDOWN_HOURS=3
```

## Get Leetify API Key
### Run

Visit [Leetify API Docs](https://api-public-docs.cs-prod.leetify.com)
```bash
npm start
```

## Attribution

This application uses data **Powered by Leetify**. CS2 Roast Bot is an independent application and is not an official Leetify app or endorsed by Leetify.

- All player statistics are sourced from Leetify
- Visit [Leetify](https://leetify.com) for detailed CS2 player analytics
Powered by Leetify. All player statistics are sourced from [Leetify](https://leetify.com). This is an independent application and is not officially endorsed by Leetify.

![Powered by Leetify](assets/Leetify%20Badge%20White%20Small.png)

Expand Down
57 changes: 21 additions & 36 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const config = require('./config');
const matchTracker = require('./services/matchTracker');
const { deleteGuildConfig } = require('./utils/guildConfigManager');
const { removeGuildFromAllUsers } = require('./utils/userLinksManager');
const { deployCommandsToGuild, removeCommandsFromGuild } = require('./utils/commandDeployer');
const { removeCommandsFromGuild } = require('./utils/commandDeployer');
const { deployGlobalCommands } = require('./utils/globalCommandDeployer');
const {
trackGuild,
markCommandsDeployed,
Expand Down Expand Up @@ -128,6 +129,15 @@ client.once('clientReady', async () => {
console.log('Bot is ready to roast some CS2 players!');
client.user.setActivity('CS2 players', { type: ActivityType.Watching });

// Deploy commands globally with user install support
console.log('[STARTUP] Deploying global commands...');
const success = await deployGlobalCommands();
if (success) {
console.log('[STARTUP] Global commands deployed successfully');
} else {
console.error('[STARTUP] Failed to deploy global commands');
}

// Detect new guilds that joined while bot was offline
console.log('[STARTUP] Checking for new guilds joined while offline...');
const newGuilds = await detectNewGuilds(client);
Expand All @@ -137,29 +147,11 @@ client.once('clientReady', async () => {
for (const guild of newGuilds) {
console.log(` - ${guild.name} (${guild.id})`);
}

// Deploy commands to new guilds in parallel
console.log(`[STARTUP] Deploying commands to ${newGuilds.length} new guild(s) in parallel...`);
const deploymentPromises = newGuilds.map(guild =>
deployCommandsToGuild(guild.id)
.then(success => {
if (success) {
markCommandsDeployed(guild.id);
console.log(`[STARTUP] Successfully deployed commands to ${guild.name}`);
} else {
console.error(`[STARTUP] Failed to deploy commands to ${guild.name}`);
}
return { guild, success };
})
.catch(error => {
console.error(`[STARTUP] Error deploying to ${guild.name}:`, error);
return { guild, success: false, error };
}),
);

const results = await Promise.allSettled(deploymentPromises);
const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
console.log(`[STARTUP] Command deployment complete: ${successCount}/${newGuilds.length} successful`);
// Note: Commands are now deployed globally, so no per-guild deployment needed
// Just mark them as detected for tracking purposes
for (const guild of newGuilds) {
markCommandsDeployed(guild.id);
}
} else {
console.log('[STARTUP] No new guilds detected.');
}
Expand Down Expand Up @@ -209,19 +201,12 @@ client.on('guildCreate', async (guild) => {
// Track the guild
trackGuild(guild.id, guild.name, true);

// Deploy slash commands to the new guild
console.log(`[GUILD JOIN] Deploying slash commands to ${guild.name}...`);
const success = await deployCommandsToGuild(guild.id);

if (success) {
markCommandsDeployed(guild.id);
console.log(`[GUILD JOIN] Commands deployed successfully to ${guild.name}`);
// Note: Commands are deployed globally, no per-guild deployment needed
markCommandsDeployed(guild.id);
console.log('[GUILD JOIN] Guild tracked. Commands are available globally.');

// Send onboarding message
await sendOnboardingMessage(guild);
} else {
console.error(`[GUILD JOIN] Failed to deploy commands to ${guild.name}`);
}
// Send onboarding message
await sendOnboardingMessage(guild);
});

// Handle bot being removed from a guild
Expand Down
Loading