Youtube Together is a lightweight Discord bot built to let users watch YouTube videos synchronously within Discord voice channels. It acts as an integration layer between Discord's native voice channel activities and the traditional bot command interface, using the discord-together package to generate activity invite links.
- About the Project
- Key Features
- Tech Stack
- System Architecture
- Folder Structure
- Important Code Concepts
- Architectural Decisions
- Data Model
- Main User Flows
- Setup Instructions
- Available Scripts
- Configuration Notes
- Testing
- Deployment
- Future Improvements
- Learning Outcomes
- Screenshots
- License
This project solves a specific problem for Discord users: accessing Discord's hidden or experimental voice channel activities. The bot listens for a simple command (yt!youtube) and programmatically generates a temporary invite link that triggers the YouTube Together activity when clicked.
The current implementation is deliberately kept simple. It runs entirely in memory without a connected database, as it only needs to read the user's current voice channel state and communicate with Discord's API. Note that based on the current code, the activity generated only works on the desktop client, which is a known limitation of the underlying Discord activity implementation.
- Activity Generation (
yt!youtube) If a user is inside a voice channel, the bot uses thediscord-togetherclient to generate a YouTube activity link tied to that specific channel. - Basic Error Handling
The bot intercepts
CommandInvokeErrorevents to provide a helpful message if a user tries to run the activity command without being in a voice channel. - Bot Analytics/Logging The current code tracks when the bot joins or leaves a server, logging the server name, ID, member count, and region to a hardcoded developer channel. It also dynamically updates its status presence to show the number of servers it is in.
| Layer | Technology | Purpose |
|---|---|---|
| Runtime | Python 3.8+ | Core execution environment |
| Framework | discord / discord.py (v1.7.3) | Handles the Discord API connection, events, and command routing |
| Integration | discord-together (v1.0.7) | Bridges the bot with Discord's undocumented voice activities API |
| Package Manager | Poetry | Manages dependencies and creates the virtual environment |
The bot uses an event-driven architecture based on discord.py. Everything is contained in a single process.
User (in Voice Channel)
↓
Types `yt!youtube`
↓
Discord Event Loop (main.py)
↓
Command Router checks context
↓
discord-together API Call
↓
Discord Voice Channel generates activity
↓
Bot returns Embed with Link to UserThe repository is kept as a minimal Python project.
./
main.py # Main entry point containing all bot commands and events
pyproject.toml # Poetry configuration and dependency definitions
poetry.lock # Locked dependency versions- Command Context (
ctx) The bot relies heavily on the context object provided bydiscord.ext.commandsto verify the state of the user. For example, it readsctx.author.voice.channel.idto know where to bind the YouTube activity. - Event Listeners (
@client.event) We use event hooks likeon_guild_joinandon_guild_removeto trigger side effects. Currently, these construct detailed embeds and push them to a specific internal channel for basic usage tracking. - External Integration (
DiscordTogether) Thediscord-togetherclass is initialized alongside the main bot client and manages the HTTP requests necessary to start the hidden Discord activities.
- Single-File Entry Point
Currently, all commands, events, and error handling live inside
main.py. This structure suggests the project was designed as a quick proof-of-concept. The tradeoff is that as the bot adds more commands or activities, this file will become difficult to navigate. - Environment-Based Secrets
The bot uses
os.environ['TOKEN']to authenticate. This prevents the secret from being hardcoded in the source, which is standard practice, but it also means the app will immediately crash with aKeyErrorif the environment isn't set up correctly. - No Persistence Layer The application does not use a database. Given the current feature set, this tradeoff makes sense because the bot's only job is to react to immediate user commands and format API responses.
Because the bot relies entirely on real-time data from the Discord API and does not persist user information, there is no formal database schema or data model in this repository. The main data structures dealt with are transient discord.Guild, discord.Member, and discord.Embed objects.
Starting a Watch Party
- A Discord user joins a voice channel.
- The user types
yt!youtubein a text channel. - The bot reads the user's voice state and passes the channel ID to
togetherControl.create_link. - The bot replies with an embed containing a clickable hyperlink.
- The user clicks the link, which opens the YouTube player inside their Discord voice channel UI.
- Python 3.8 or higher
- Poetry installed globally (
pip install poetryorpipx install poetry) - A registered Discord Bot application with a valid token
Clone the repository and use Poetry to install dependencies:
git clone <repository-url>
cd <repository-folder>
poetry installThe application requires one environment variable to authenticate with Discord.
TOKEN=your_discord_bot_token_hereTo start the bot, run the main script inside the Poetry environment. You must pass the token inline or set it in your environment beforehand.
TOKEN=your_token_here poetry run python main.pySince this project uses Poetry, you can run the main file using:
| Command | Description |
|---|---|
poetry run python main.py |
Starts the bot process |
poetry env info |
Shows details about the virtual environment |
pyproject.tomlconfigures the project metadata and exact dependency bounds fordiscord.pyanddiscord-together.- The bot prefix is hardcoded as
yt!incommands.Bot(command_prefix="yt!").
Automated tests are not currently included in the repository.
Since the project relies heavily on async API interactions, testing this would likely require a framework like pytest-asyncio combined with a mocking library (like dpytest) to simulate Discord contexts and command invocations.
No deployment-specific configuration (like Dockerfiles or Procfiles) was found. Since this is a standalone Python process, it can generally be deployed to a basic VPS or a service like Render or Heroku by wrapping the run command in a simple startup script and providing the TOKEN environment variable.
- Refactor Command Structure: Split commands and events into Discord Cogs instead of keeping everything in
main.py. This will improve maintainability. - Better Configuration Handling: Use a
.envfile parser (likepython-dotenv) so developers don't have to manually export the token into their shell environment. - Remove Hardcoded Channel IDs: The
log_channelID inside the guild join/remove events (832944803338911765) is hardcoded. This should be moved to a configuration file or environment variable so anyone can deploy their own version of the bot without hitting an error. - Help Command Customization: The current
helpcommand is a basic string response. It could be upgraded to a formatted embed.
This project demonstrates practical experience working with external APIs and asynchronous Python programming. It shows an understanding of event-driven architectures, where an application must sit idle and react predictably to unpredictable user input. Setting up the CommandInvokeError handler specifically shows an awareness of how user error (like using the command outside a voice channel) can break assumptions in the code.
Screenshots can be added here to show the bot's embeds and the YouTube activity interface inside Discord.License information has not been specified yet.