Documentation Index Fetch the complete documentation index at: https://mintlify.com/Nuu-maan/Filly-Discord-Token-Filler/llms.txt
Use this file to discover all available pages before exploring further.
Filly uses Python’s concurrent.futures.ThreadPoolExecutor to manage multiple tokens joining servers simultaneously. This advanced threading implementation maximizes throughput while maintaining stability.
Threading Architecture
ThreadPoolExecutor Implementation
The main execution loop (index.py:264-279) uses a thread pool to process multiple tokens concurrently:
for invite in invites[:max_joins]:
with concurrent.futures.ThreadPoolExecutor( max_workers = max_threads) as executor:
for tokenz, token in enumerate (token_manager.tokens):
joins = token_manager.increment_joins(token)
if joins >= config.max_joins:
with open ( "output/filled_tokens.txt" , "a" ) as f:
f.write( f " { token } \n " )
continue
if stats.invalid > 0 or stats.locked > 0 :
break
thread_number = (tokenz % max_threads) + 1
joiner = DiscordJoiner(thread_number, config, stats)
executor.submit(joiner.join_server, token.strip(), invite)
Thread Count Calculation
The number of threads is dynamically calculated (index.py:249):
max_threads = min ( len (token_manager.tokens), config.threads)
This ensures:
Never more threads than available tokens
Respects the threads setting in config.json
Optimal resource utilization
Configuration
Set thread count in input/config.json:
{
"threads" : 5 ,
"delay" : 0 ,
"max_joins" : 50
}
Setting threads too high (>10) may trigger Discord rate limiting. Recommended: 3-5 threads for best results.
Thread-Safe Components
1. Statistics Tracking
The JoinerStats dataclass (index.py:41-49) is shared across all threads:
@dataclass
class JoinerStats :
total: int = 0
joined: int = 0
captcha: int = 0
captcha_solved: int = 0
failed: int = 0
locked: int = 0
invalid: int = 0
current: int = 0
All threads increment these counters atomically during execution.
2. Thread-Safe Logging
The NovaLogger class uses a threading lock (data/logger.py:9-54):
class NovaLogger :
_lock = Lock()
_log_file = None
@ classmethod
def _write ( cls , message ):
"""Thread-safe logging"""
with cls ._lock:
if cls ._log_file:
clean_message = cls ._strip_colors(message)
cls ._log_file.write(clean_message + ' \n ' )
cls ._log_file.flush()
print (message + Style. RESET_ALL )
This prevents log corruption when multiple threads write simultaneously.
3. Token Manager
The TokenManager class (index.py:51-66) handles thread-safe token operations:
class TokenManager :
def __init__ ( self , tokens_file : str ):
self .tokens_file = Path(tokens_file)
self .tokens = self ._load_tokens()
self .token_joins: Dict[ str , int ] = {}
def _load_tokens ( self ) -> List[ str ]:
return list ( set ( self .tokens_file.read_text().splitlines()))
def remove_token ( self , token : str ) -> None :
self .tokens = [t for t in self .tokens if t.strip() != token]
self .tokens_file.write_text( ' \n ' .join( self .tokens) + ' \n ' )
def increment_joins ( self , token : str ) -> int :
self .token_joins[token] = self .token_joins.get(token, 0 ) + 1
return self .token_joins[token]
Per-Thread Components
DiscordJoiner Instance
Each thread creates its own DiscordJoiner instance (index.py:277-279):
thread_number = (tokenz % max_threads) + 1
joiner = DiscordJoiner(thread_number, config, stats)
executor.submit(joiner.join_server, token.strip(), invite)
Thread Number Assignment:
Uses modulo operation: (tokenz % max_threads) + 1
Ensures thread numbers cycle from 1 to max_threads
Example with 5 threads: Token 0→Thread 1, Token 5→Thread 1, Token 7→Thread 3
Session Management
Each thread maintains isolated HTTP sessions (index.py:73-88):
def _get_session ( self ):
try :
return request.Session( impersonate = "chrome" ), request.Session( impersonate = "chrome" )
except Exception :
return self ._get_session()
def _setup_session ( self ):
if not self .config.proxyless:
proxies = Path( 'input/proxies.txt' ).read_text().splitlines()
self .proxy = f "http:// { random.choice(proxies) } " .replace(
'sessionid' , str (random.randint( 1327390889 , 1399999999 ))
)
self .session.proxies = { "http" : self .proxy, "https" : self .proxy}
Each thread gets:
Two independent sessions (main session + client for captcha retries)
Random proxy selection
Unique session IDs
Title Update Thread
A separate daemon thread updates the console title in real-time (index.py:254-259):
title_thread = threading.Thread(
target = update_title,
args = (stats, done, len (invites))
)
title_thread.daemon = True # Make thread daemon so it exits with main program
title_thread.start()
Update Function
def update_title ( stats : JoinerStats, done : threading.Event, total_invites : int ):
"""Updates the console title with current joining statistics."""
while not done.is_set():
try :
ctypes.windll.kernel32.SetConsoleTitleW(
f "Filly | "
f "Total: { total_invites } | "
f "Joined: { stats.joined } | "
f "Failed: { stats.failed } | "
f "Captcha: { stats.captcha } ( { stats.captcha_solved } solved) | "
f "Invalid: { stats.invalid } | "
f "Locked: { stats.locked } "
)
time.sleep( 0.1 )
except Exception as e:
NovaLogger.fail( f "Title Update Error: { str (e) } " )
break
Update frequency: Every 0.1 seconds (100ms)
Thread Lifecycle
1. Initialization
start_time = time.time()
done = threading.Event()
max_threads = min ( len (token_manager.tokens), config.threads)
2. Execution
with concurrent.futures.ThreadPoolExecutor( max_workers = max_threads) as executor:
executor.submit(joiner.join_server, token.strip(), invite)
The with statement ensures:
Automatic thread cleanup
Waits for all submitted tasks to complete
Proper resource deallocation
3. Cleanup
Session cleanup happens in each thread (index.py:215-221):
finally :
self .session.close()
self .client.close()
if self .config.delay > 0 :
NovaLogger.note( f "Sleeping for { self .config.delay } seconds" )
time.sleep( self .config.delay)
4. Shutdown
Main thread cleanup (index.py:282-284):
finally :
# Signal title thread to stop and wait for it
done.set()
title_thread.join( timeout = 1.0 ) # Add timeout to prevent hanging
Break Conditions
Threads stop early if critical errors occur (index.py:274-275):
if stats.invalid > 0 or stats.locked > 0 :
break
This prevents wasting API calls when tokens are invalid or locked.
Token Filling Logic
Tokens that reach max joins are saved and skipped (index.py:269-272):
if joins >= config.max_joins:
with open ( "output/filled_tokens.txt" , "a" ) as f:
f.write( f " { token } \n " )
continue
Best Practices
Thread Count
Start with 3-5 threads
Monitor for rate limiting (429 errors)
Increase gradually if no errors occur
Delay Configuration
Set delay: 0 for maximum speed
Use delay: 1-2 to avoid rate limits
Higher delays reduce captcha likelihood
Max Joins
Limit max_joins to prevent excessive API usage
Track filled tokens in output/filled_tokens.txt
Why use ThreadPoolExecutor instead of threading.Thread?
ThreadPoolExecutor provides:
Automatic thread pool management
Task queue handling
Built-in cleanup via context manager
Better resource utilization
Simpler error handling
Manual threading requires managing thread lifecycle, which is error-prone.
Can I use async/await instead of threads?
While async could work, threading is preferred because:
Captcha solving involves long polling (blocking operations)
External HTTP libraries (curl_cffi) are synchronous
ThreadPoolExecutor handles blocking I/O efficiently
Async would require rewriting all HTTP calls and solver logic.
Monitoring Thread Activity
Watch the console output to monitor thread activity:
[12:34:56] [✓] Successfully Joined Server ▸ token=ABC123.* thread=1
[12:34:57] [△] Captcha Detected ▸ token=DEF456.*
[12:34:58] [✓] Captcha Solved Successfully
The thread=N parameter shows which thread processed each token.
If you see many threads with the same number, your thread count may be too low. Increase the threads value in config.json.