Documentation Index
Fetch the complete documentation index at: https://mintlify.com/FujiwaraChoki/MoneyPrinterV2/llms.txt
Use this file to discover all available pages before exploring further.
MoneyPrinter V2 is designed with modularity in mind. This guide explains the project structure and shows you how to extend functionality.
Project Structure
Understanding the codebase organization is key to customization:
MoneyPrinterV2/
├── src/ # Core application code
│ ├── main.py # CLI entrypoint
│ ├── config.py # Configuration management
│ ├── utils.py # Shared utilities
│ ├── cache.py # Cache management
│ ├── constants.py # Application constants
│ ├── status.py # Status/logging utilities
│ ├── llm_provider.py # LLM provider abstraction
│ ├── cron.py # Scheduled task runner
│ ├── art.py # ASCII art for CLI
│ └── classes/ # Provider implementations
│ ├── YouTube.py # YouTube automation
│ ├── Twitter.py # Twitter/X automation
│ ├── Tts.py # Text-to-speech
│ ├── AFM.py # Affiliate marketing
│ └── Outreach.py # Cold outreach automation
├── scripts/ # Helper scripts
│ ├── setup_local.sh
│ ├── preflight_local.py
│ └── upload_video.sh
├── fonts/ # Font files for video rendering
├── assets/ # Static assets
├── docs/ # Documentation
├── config.json # User configuration (gitignored)
├── config.example.json # Configuration template
└── requirements.txt # Python dependencies
Configuration System
All user-facing configuration lives in config.json. The src/config.py module provides getter functions:
Adding New Config Options
1. Add to config.example.json:
{
"my_custom_option": "default_value"
}
2. Create getter in src/config.py:
def get_my_custom_option() -> str:
"""
Gets the custom option from config.
Returns:
value (str): The custom option value
"""
with open(os.path.join(ROOT_DIR, "config.json"), "r") as file:
return json.load(file).get("my_custom_option", "default_value")
3. Use anywhere in the codebase:
from config import get_my_custom_option
value = get_my_custom_option()
Configuration Best Practices
- Use descriptive names (
whisper_model, not wm)
- Provide sensible defaults with
.get(key, default)
- Support environment variables for sensitive data:
return json.load(file).get("api_key", "") or os.environ.get("API_KEY", "")
- Document options in
config.example.json
Extending Provider Classes
YouTube Class Extension
The YouTube class (src/classes/YouTube.py:35) handles video generation and upload. Here’s how to extend it:
Example: Add Custom Video Effects
from src.classes.YouTube import YouTube
from moviepy.video.fx.all import fadein, fadeout
class CustomYouTube(YouTube):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def apply_custom_effects(self, clip):
"""
Apply custom video effects to a clip.
Args:
clip: MoviePy video clip
Returns:
Modified clip with effects applied
"""
# Add fade in/out effects
clip = clip.fx(fadein, 1.0)
clip = clip.fx(fadeout, 1.0)
# Add your custom effects here
# Example: color correction, filters, etc.
return clip
def combine(self) -> str:
"""
Override combine method to add custom effects.
"""
# Call parent method to do standard processing
path = super().combine()
# Post-process the generated video
from moviepy.editor import VideoFileClip
video = VideoFileClip(path)
video = self.apply_custom_effects(video)
# Write the modified video
output_path = path.replace(".mp4", "_custom.mp4")
video.write_videofile(output_path, threads=get_threads())
return output_path
Example: Custom Prompt Generation
class CustomYouTube(YouTube):
def generate_prompts(self) -> List[str]:
"""
Override to use a custom prompt generation strategy.
"""
# Use your own prompting logic
custom_prompt = f"""
Generate exactly 5 detailed image prompts for: {self.subject}
Requirements:
- Each prompt must be cinematic and visually striking
- Include lighting, composition, and mood details
- Match the tone: {self.niche}
Return as JSON array.
"""
completion = self.generate_response(custom_prompt)
image_prompts = json.loads(completion)
self.image_prompts = image_prompts
return image_prompts
Using Your Custom Class:
# In src/cron.py or your own script
from my_custom_youtube import CustomYouTube
youtube = CustomYouTube(
account_uuid="my-account",
account_nickname="My Channel",
fp_profile_path="/path/to/firefox/profile",
niche="Technology",
language="English"
)
video_path = youtube.generate_video(tts_instance)
The Twitter class (src/classes/Twitter.py:24) handles social media automation.
Example: Custom Post Formatting
from src.classes.Twitter import Twitter
class CustomTwitter(Twitter):
def __init__(self, *args, hashtags=None, **kwargs):
super().__init__(*args, **kwargs)
self.hashtags = hashtags or []
def generate_post(self) -> str:
"""
Override to add custom hashtags and formatting.
"""
# Get base content from LLM
content = super().generate_post()
# Add custom formatting
# Example: add line breaks for readability
lines = content.split(". ")
formatted = "\n\n".join(lines)
# Add hashtags
if self.hashtags:
hashtag_string = " ".join([f"#{tag}" for tag in self.hashtags])
formatted = f"{formatted}\n\n{hashtag_string}"
# Ensure it fits in Twitter's character limit
if len(formatted) > 280:
formatted = formatted[:277] + "..."
return formatted
Example: Add Image Attachments
class CustomTwitter(Twitter):
def post_with_image(self, text: str, image_path: str) -> None:
"""
Post tweet with an attached image.
Args:
text: Tweet content
image_path: Path to image file
"""
bot = self.browser
bot.get("https://x.com/compose/post")
# Find and fill text box
text_box = self.wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "div[data-testid='tweetTextarea_0'][role='textbox']")
)
)
text_box.click()
text_box.send_keys(text)
# Upload image
file_input = bot.find_element(
By.CSS_SELECTOR, "input[data-testid='fileInput']"
)
file_input.send_keys(os.path.abspath(image_path))
time.sleep(2) # Wait for upload
# Click post button
post_button = self.wait.until(
EC.element_to_be_clickable(
(By.XPATH, "//button[@data-testid='tweetButton']")
)
)
post_button.click()
time.sleep(2)
self.add_post({"content": text, "image": image_path, "date": datetime.now().strftime("%m/%d/%Y, %H:%M:%S")})
Adding New Providers
To add support for a new platform (e.g., TikTok, Instagram):
1. Create a new class file: src/classes/TikTok.py
import os
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from webdriver_manager.firefox import GeckoDriverManager
from config import get_firefox_profile_path, get_headless
from status import info, success, error
class TikTok:
"""
Automation class for TikTok.
"""
def __init__(self, account_uuid: str, fp_profile_path: str, niche: str):
self.account_uuid = account_uuid
self.fp_profile_path = fp_profile_path
self.niche = niche
# Initialize Selenium
self.options = Options()
if get_headless():
self.options.add_argument("--headless")
self.options.add_argument("-profile")
self.options.add_argument(fp_profile_path)
self.service = Service(GeckoDriverManager().install())
self.browser = webdriver.Firefox(
service=self.service,
options=self.options
)
def upload_video(self, video_path: str, title: str, description: str) -> bool:
"""
Upload a video to TikTok.
Args:
video_path: Path to video file
title: Video title
description: Video description
Returns:
success: Whether upload succeeded
"""
try:
self.browser.get("https://www.tiktok.com/upload")
time.sleep(3)
# Find file input and upload
file_input = self.browser.find_element(
By.CSS_SELECTOR, "input[type='file']"
)
file_input.send_keys(os.path.abspath(video_path))
# Fill caption/description
caption_box = self.browser.find_element(
By.CSS_SELECTOR, "div[contenteditable='true']"
)
caption_box.click()
caption_box.send_keys(f"{title}\n\n{description}")
# Click post button
post_button = self.browser.find_element(
By.XPATH, "//button[contains(text(), 'Post')]"
)
post_button.click()
time.sleep(5)
success(f"Uploaded video to TikTok: {title}")
return True
except Exception as e:
error(f"Failed to upload to TikTok: {e}")
return False
finally:
self.browser.quit()
2. Add configuration options in config.json:
{
"tiktok_accounts": [
{
"id": "tiktok-account-1",
"niche": "Comedy",
"firefox_profile": "/path/to/profile"
}
]
}
3. Create helper functions in src/config.py:
def get_tiktok_accounts() -> List[dict]:
with open(os.path.join(ROOT_DIR, "config.json"), "r") as file:
return json.load(file).get("tiktok_accounts", [])
4. Integrate into src/cron.py or CLI:
from classes.TikTok import TikTok
def run_tiktok_automation(account_id: str):
accounts = get_tiktok_accounts()
account = next((a for a in accounts if a["id"] == account_id), None)
if not account:
error(f"TikTok account {account_id} not found")
return
tiktok = TikTok(
account_uuid=account["id"],
fp_profile_path=account["firefox_profile"],
niche=account["niche"]
)
# Generate video using YouTube class logic
# (or create separate TikTok video generation)
video_path = generate_tiktok_video(account["niche"])
tiktok.upload_video(
video_path=video_path,
title="My TikTok Video",
description="Generated with MoneyPrinter V2"
)
Custom LLM Providers
The src/llm_provider.py module abstracts LLM calls. To add a new provider:
Example: Add Anthropic Claude
# In src/llm_provider.py
import anthropic
from config import get_anthropic_api_key
def generate_text_anthropic(prompt: str, model: str = "claude-3-5-sonnet-20241022") -> str:
"""
Generate text using Anthropic Claude API.
"""
client = anthropic.Anthropic(api_key=get_anthropic_api_key())
message = client.messages.create(
model=model,
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def generate_text(prompt: str, model_name: str = None) -> str:
"""
Main entry point for text generation.
"""
provider = get_llm_provider() # From config.json
if provider == "anthropic":
return generate_text_anthropic(prompt, model_name)
elif provider == "local_ollama":
return generate_text_ollama(prompt, model_name)
# ... other providers
Update config.json:
{
"llm_provider": "anthropic",
"anthropic_api_key": "sk-ant-...",
"anthropic_model": "claude-3-5-sonnet-20241022"
}
Modifying Prompts
Prompts are scattered throughout provider classes. To systematically customize them:
Centralize prompts in src/prompts.py:
# src/prompts.py
def get_youtube_script_prompt(subject: str, language: str, sentence_length: int) -> str:
return f"""
Generate a {sentence_length}-sentence video script about: {subject}
Requirements:
- Language: {language}
- Engaging hook in first sentence
- Clear structure with beginning, middle, end
- Conversational tone
- No markdown formatting
Output only the raw script text.
"""
def get_youtube_title_prompt(subject: str) -> str:
return f"""
Generate a compelling YouTube title for: {subject}
Requirements:
- Under 100 characters
- Include relevant hashtags
- Clickable but not clickbait
Output only the title.
"""
def get_image_prompt_template(subject: str, script: str, n_prompts: int) -> str:
return f"""
Generate {n_prompts} detailed AI image generation prompts for: {subject}
Context: {script}
Requirements:
- Photorealistic, cinematic style
- Include lighting, composition, mood
- Relevant to the script narrative
Output as JSON array: ["prompt1", "prompt2", ...]
"""
Use in classes:
# In src/classes/YouTube.py
from prompts import get_youtube_script_prompt
class YouTube:
def generate_script(self) -> str:
prompt = get_youtube_script_prompt(
subject=self.subject,
language=self.language,
sentence_length=get_script_sentence_length()
)
completion = self.generate_response(prompt)
# ... rest of method
This makes it easy to A/B test prompts or swap them based on niche.
Testing Your Extensions
When developing custom features:
1. Use verbose mode:
2. Test in isolation:
# test_custom_youtube.py
from my_extensions import CustomYouTube
from classes.Tts import TTS
if __name__ == "__main__":
youtube = CustomYouTube(
account_uuid="test",
account_nickname="Test",
fp_profile_path="/path/to/profile",
niche="Technology",
language="English"
)
# Test individual methods
topic = youtube.generate_topic()
print(f"Topic: {topic}")
script = youtube.generate_script()
print(f"Script: {script}")
3. Run preflight checks:
python3 scripts/preflight_local.py
4. Monitor logs:
The status.py module provides logging:
from status import info, success, warning, error
info("Starting custom process...")
success("Process completed successfully!")
warning("Non-critical issue detected")
error("Critical failure occurred")
Contributing Extensions
If you build something useful:
- Document your changes in
docs/
- Update
config.example.json with new options
- Add examples and usage instructions
- Submit a pull request to the main repository
- See CONTRIBUTING.md for guidelines
Community extensions help everyone! Share your customizations on the Discord.