Skip to content

Commit 58eb09f

Browse files
Add create-team-voice command to generate private voice channels for teams; implement error handling and logging
1 parent 879d5d2 commit 58eb09f

File tree

3 files changed

+452
-0
lines changed

3 files changed

+452
-0
lines changed

cogs/teams_cog.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class TeamsCog(commands.Cog):
1111

1212
def __init__(self, bot):
1313
self.bot = bot
14+
self.voice_category_id = 1357422869528838236
1415

1516
@app_commands.command(name="my-team", description="View your team and its members")
1617
async def my_team_command(self, interaction: discord.Interaction):
@@ -327,5 +328,68 @@ async def sync_matcherino_teams(self):
327328
logger.error(f"Error during team sync: {e}")
328329
raise
329330

331+
@app_commands.command(name="create-team-voice", description="Create private voice channels for all teams")
332+
@app_commands.default_permissions(administrator=True)
333+
async def create_team_voice_channels(self, interaction: discord.Interaction):
334+
"""Admin command to create private voice channels for all teams."""
335+
await interaction.response.defer(ephemeral=True)
336+
337+
try:
338+
guild = interaction.guild
339+
category = guild.get_channel(self.voice_category_id)
340+
341+
if not category:
342+
await interaction.followup.send(f"Could not find the category with ID {self.voice_category_id}", ephemeral=True)
343+
return
344+
345+
# Get all active teams
346+
teams = await self.bot.db.get_active_teams()
347+
if not teams:
348+
await interaction.followup.send("No active teams found.", ephemeral=True)
349+
return
350+
351+
channels_created = 0
352+
for team in teams:
353+
# Get team members' Discord IDs
354+
team_info = await self.bot.db.get_team_members(team['team_id'])
355+
member_ids = [int(member['discord_user_id']) for member in team_info['members'] if member.get('discord_user_id')]
356+
357+
if not member_ids:
358+
continue
359+
360+
# Create overwrites for the channel
361+
overwrites = {
362+
guild.default_role: discord.PermissionOverwrite(view_channel=False),
363+
guild.me: discord.PermissionOverwrite(view_channel=True, manage_channels=True)
364+
}
365+
366+
# Add overwrites for each team member
367+
for member_id in member_ids:
368+
member = guild.get_member(member_id)
369+
if member:
370+
overwrites[member] = discord.PermissionOverwrite(view_channel=True, connect=True, speak=True)
371+
372+
# Create the voice channel
373+
channel_name = f"🎮 {team['team_name']}"
374+
try:
375+
await guild.create_voice_channel(
376+
name=channel_name,
377+
category=category,
378+
overwrites=overwrites
379+
)
380+
channels_created += 1
381+
except Exception as e:
382+
logger.error(f"Error creating voice channel for team {team['team_name']}: {e}")
383+
continue
384+
385+
await interaction.followup.send(
386+
f"Created {channels_created} team voice channels in category '{category.name}'.",
387+
ephemeral=True
388+
)
389+
390+
except Exception as e:
391+
logger.error(f"Error in create-team-voice command: {e}")
392+
await interaction.followup.send(f"An error occurred: {str(e)}", ephemeral=True)
393+
330394
async def setup(bot):
331395
await bot.add_cog(TeamsCog(bot))

utils/create_teams.py

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
"""
2+
Create teams from the first participants that signed up on Matcherino.
3+
This script will fetch the first X participants needed to create the specified number of teams.
4+
"""
5+
6+
import os
7+
import asyncio
8+
import logging
9+
import random
10+
import aiohttp
11+
from typing import List, Dict, Any, Optional, Tuple
12+
import sys
13+
14+
# Add the parent directory to sys.path to import from parent directory
15+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16+
from matcherino_scraper import MatcherinoScraper
17+
18+
# Configure logging
19+
logging.basicConfig(
20+
level=logging.INFO,
21+
format="%(asctime)s [%(levelname)s] %(message)s",
22+
handlers=[logging.StreamHandler()]
23+
)
24+
logger = logging.getLogger(__name__)
25+
26+
# Configuration
27+
DEFAULT_TEAM_COUNT = 128
28+
PLAYERS_PER_TEAM = 3
29+
DELAY_BETWEEN_TEAMS = 1.0 # seconds
30+
DELAY_BETWEEN_MEMBERS = 0.5 # seconds
31+
32+
# Lists of adjectives and brainrot terms (divided by category)
33+
adjectives = [
34+
"Screaming", "Gooning", "Silent", "Edged", "Rizzed", "Cooking", "Slippery",
35+
"Bussin'", "Vaped", "Nonchalant", "Digital", "Grilled", "Bizarre", "Dynamic",
36+
"Fierce", "Mighty", "Radical", "Epic", "Wild", "Electric"
37+
]
38+
39+
# Characters/People
40+
characters = [
41+
"Skibidi Toilet", "Baby Gronk", "Duke Dennis", "Kai Cenat",
42+
"IShowSpeed", "Grimace", "Quandale Dingle", "Livvy Dunne",
43+
"Sigma Male", "Chris Tyson", "Fanum"
44+
]
45+
46+
# Objects/Things
47+
objects_things = [
48+
"Grimace Shake", "Glizzy", "Gyatt", "Aura", "Fanta",
49+
"Life Saver Gummies", "Digital Circus", "Imposter",
50+
"Cap", "L", "Ratio", "Brisket", "Mewing", "Ohio"
51+
]
52+
53+
# Phrases/Concepts
54+
phrases = [
55+
"Let Him Cook", "On Skibidi", "L + Ratio", "Stop the Cap",
56+
"Goonmaxxing", "Looksmaxxing", "Biting the Curb", "Only in Ohio",
57+
"Pray Today", "1 2 Buckle My Shoe"
58+
]
59+
60+
# Combine all brainrot terms into one list
61+
all_terms = characters + objects_things + phrases
62+
63+
def generate_team_name() -> str:
64+
"""Generate a single random team name by pairing an adjective with a brainrot term."""
65+
return f"{random.choice(adjectives)} {random.choice(all_terms)}"
66+
67+
async def create_team(session: aiohttp.ClientSession, auth_token: str, bounty_id: str = "146289") -> Tuple[Optional[int], Optional[str]]:
68+
"""
69+
Create a team on Matcherino using the API.
70+
71+
Args:
72+
session: aiohttp ClientSession for making requests
73+
auth_token: Bearer token for authentication
74+
bounty_id: The bounty/tournament ID (default: 146289)
75+
76+
Returns:
77+
Tuple of (team_id, team_name) if successful, (None, None) if failed
78+
"""
79+
team_name = generate_team_name()
80+
url = "https://api.matcherino.com/__api/teams/bounties/create"
81+
headers = {
82+
"Accept": "*/*",
83+
"Accept-Encoding": "gzip, deflate, br, zstd",
84+
"Accept-Language": "en-US,en;q=0.9",
85+
"Content-Type": "text/plain;charset=UTF-8",
86+
"Origin": "https://api.matcherino.com",
87+
"Referer": "https://api.matcherino.com/__api/session/corsPreflightBypass",
88+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
89+
"x-mno-auth": f"Bearer {auth_token}"
90+
}
91+
92+
# Convert payload to JSON string since we're using text/plain content type
93+
payload = f'{{"temporary":true,"name":"{team_name}","bountyId":{bounty_id}}}'
94+
95+
try:
96+
async with session.post(url, data=payload, headers=headers) as response:
97+
if response.status != 200:
98+
response_text = await response.text()
99+
logger.error(f"Failed to create team. Status: {response.status}, Response: {response_text}")
100+
return None, None
101+
102+
data = await response.json()
103+
team_id = data["body"]["id"]
104+
logger.info(f"Created team '{team_name}' with ID: {team_id}")
105+
return team_id, team_name
106+
107+
except Exception as e:
108+
logger.error(f"Error creating team: {e}")
109+
return None, None
110+
111+
async def add_member_to_team(session: aiohttp.ClientSession, auth_token: str, team_id: int, user_id: int) -> bool:
112+
"""
113+
Add a member to a team using the Matcherino API.
114+
115+
Args:
116+
session: aiohttp ClientSession for making requests
117+
auth_token: Bearer token for authentication
118+
team_id: The ID of the team to add the member to
119+
user_id: The user ID of the member to add
120+
121+
Returns:
122+
bool: True if successful, False if failed
123+
"""
124+
url = "https://api.matcherino.com/__api/teams/bounties/members/upsert"
125+
headers = {
126+
"Accept": "*/*",
127+
"Accept-Encoding": "gzip, deflate, br, zstd",
128+
"Accept-Language": "en-US,en;q=0.9",
129+
"Content-Type": "text/plain;charset=UTF-8",
130+
"Origin": "https://api.matcherino.com",
131+
"Referer": "https://api.matcherino.com/__api/session/corsPreflightBypass",
132+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
133+
"x-mno-auth": f"Bearer {auth_token}"
134+
}
135+
136+
# Convert payload to JSON string since we're using text/plain content type
137+
payload = f'{{"bountyTeamId":{team_id},"members":[{{"userId":{user_id}}}]}}'
138+
139+
try:
140+
async with session.post(url, data=payload, headers=headers) as response:
141+
if response.status != 200:
142+
response_text = await response.text()
143+
logger.error(f"Failed to add member {user_id} to team {team_id}. Status: {response.status}, Response: {response_text}")
144+
return False
145+
146+
logger.info(f"Successfully added member {user_id} to team {team_id}")
147+
return True
148+
149+
except Exception as e:
150+
logger.error(f"Error adding member to team: {e}")
151+
return False
152+
153+
async def create_team_with_members(session: aiohttp.ClientSession, auth_token: str, members: List[Dict[str, Any]], bounty_id: str = "146289") -> bool:
154+
"""
155+
Create a team and add its members.
156+
157+
Args:
158+
session: aiohttp ClientSession for making requests
159+
auth_token: Bearer token for authentication
160+
members: List of participant data to add as members
161+
bounty_id: The bounty/tournament ID (default: 146289)
162+
163+
Returns:
164+
bool: True if successful, False if failed
165+
"""
166+
# Create the team first
167+
team_result = await create_team(session, auth_token, bounty_id)
168+
if not team_result:
169+
return False
170+
171+
team_id, team_name = team_result
172+
173+
# Add each member to the team with a delay between requests
174+
success = True
175+
for member in members:
176+
user_id = member.get('user_id')
177+
if not user_id:
178+
logger.error(f"Missing user_id for member in team {team_name}")
179+
success = False
180+
continue
181+
182+
# Add small delay before adding member
183+
await asyncio.sleep(DELAY_BETWEEN_MEMBERS) # 500ms delay between adding members
184+
185+
if not await add_member_to_team(session, auth_token, team_id, user_id):
186+
success = False
187+
188+
return success
189+
190+
async def create_all_teams_with_members(auth_token: str, participants: List[Dict[str, Any]], team_count: int = DEFAULT_TEAM_COUNT) -> bool:
191+
"""
192+
Create the specified number of teams and add members to each.
193+
194+
Args:
195+
auth_token: Bearer token for authentication
196+
participants: List of shuffled participants to add to teams
197+
team_count: Number of teams to create
198+
199+
Returns:
200+
bool: True if all operations were successful
201+
"""
202+
async with aiohttp.ClientSession() as session:
203+
all_success = True
204+
205+
# Process participants in groups of 3 for each team
206+
for i in range(team_count):
207+
team_members = participants[i*3:(i+1)*3]
208+
if len(team_members) != 3:
209+
logger.error(f"Not enough members for team {i+1}, needed 3 but got {len(team_members)}")
210+
all_success = False
211+
break
212+
213+
logger.info(f"\nCreating team {i+1}/{team_count}")
214+
if not await create_team_with_members(session, auth_token, team_members):
215+
logger.error(f"Failed to create team {i+1} or add its members")
216+
all_success = False
217+
218+
# Add delay between creating teams
219+
await asyncio.sleep(DELAY_BETWEEN_TEAMS) # 1 second delay between teams
220+
221+
return all_success
222+
223+
async def get_recent_participants(team_count: int = DEFAULT_TEAM_COUNT, players_per_team: int = PLAYERS_PER_TEAM) -> List[Dict[str, Any]]:
224+
"""
225+
Get the first participants that signed up from Matcherino API (first come, first serve).
226+
The API returns most recent signups first, so we take from the end to get earliest signups.
227+
228+
Args:
229+
team_count (int): Number of teams to create (default: DEFAULT_TEAM_COUNT)
230+
players_per_team (int): Number of players per team (default: PLAYERS_PER_TEAM)
231+
232+
Returns:
233+
List[Dict[str, Any]]: List of participant data for the first players who signed up
234+
"""
235+
total_players_needed = team_count * players_per_team
236+
logger.info(f"Fetching first {total_players_needed} players for {team_count} teams")
237+
238+
try:
239+
async with MatcherinoScraper() as scraper:
240+
# Use the existing get_tournament_participants method
241+
# It already handles pagination and returns all participants
242+
all_participants = await scraper.get_tournament_participants("146289")
243+
244+
if not all_participants:
245+
logger.error("No participants found")
246+
return []
247+
248+
logger.info(f"Found {len(all_participants)} total participants")
249+
250+
# Take the last entries (earliest signups) since API returns newest first
251+
first_participants = all_participants[-total_players_needed:][::-1] # Reverse to get chronological order
252+
logger.info(f"Selected {len(first_participants)} earliest signups from the end of the list")
253+
254+
# Randomly shuffle the participants
255+
random.shuffle(first_participants)
256+
logger.info("Randomly shuffled all participants")
257+
258+
return first_participants
259+
260+
except Exception as e:
261+
logger.error(f"Error fetching participants: {e}", exc_info=True)
262+
return []
263+
264+
async def main():
265+
"""
266+
Main function to create teams and add members.
267+
"""
268+
auth_token = os.getenv("MATCHERINO_AUTH_TOKEN")
269+
if not auth_token:
270+
logger.error("MATCHERINO_AUTH_TOKEN environment variable not set")
271+
return
272+
273+
# Get number of teams to create, defaulting to DEFAULT_TEAM_COUNT if not specified
274+
try:
275+
team_count = int(input(f"How many teams would you like to create? (default: {DEFAULT_TEAM_COUNT}): ").strip() or DEFAULT_TEAM_COUNT)
276+
if team_count <= 0:
277+
logger.error("Number of teams must be positive")
278+
return
279+
except ValueError:
280+
logger.error("Invalid number provided")
281+
return
282+
283+
logger.info(f"Creating {team_count} teams with {PLAYERS_PER_TEAM} players each...")
284+
285+
# Get and shuffle participants
286+
participants = await get_recent_participants(team_count=team_count)
287+
if not participants:
288+
logger.error("Failed to get participants")
289+
return
290+
291+
# Create teams and add members
292+
success = await create_all_teams_with_members(auth_token, participants, team_count=team_count)
293+
if success:
294+
logger.info(f"\nSuccessfully created {team_count} teams and added members")
295+
else:
296+
logger.error("\nSome operations failed while creating teams or adding members")
297+
298+
if __name__ == "__main__":
299+
asyncio.run(main())

0 commit comments

Comments
 (0)