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"\n Creating 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"\n Successfully created { team_count } teams and added members" )
295+ else :
296+ logger .error ("\n Some operations failed while creating teams or adding members" )
297+
298+ if __name__ == "__main__" :
299+ asyncio .run (main ())
0 commit comments