- Change file mode from 644 to 755 for all project files - Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet" - Rename Miner.extra_meta_data to extra_metadata for consistency
560 lines
21 KiB
Python
Executable File
560 lines
21 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
AITBC Community Onboarding Automation
|
|
|
|
This script automates the onboarding process for new community members,
|
|
including welcome messages, resource links, and initial guidance.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Optional
|
|
from pathlib import Path
|
|
import subprocess
|
|
import os
|
|
|
|
|
|
class CommunityOnboarding:
|
|
"""Automated community onboarding system."""
|
|
|
|
def __init__(self, config_path: str = "config/community_config.json"):
|
|
self.config = self._load_config(config_path)
|
|
self.logger = self._setup_logging()
|
|
self.onboarding_data = self._load_onboarding_data()
|
|
|
|
def _load_config(self, config_path: str) -> Dict:
|
|
"""Load community configuration."""
|
|
default_config = {
|
|
"discord": {
|
|
"bot_token": os.getenv("DISCORD_BOT_TOKEN"),
|
|
"welcome_channel": "welcome",
|
|
"general_channel": "general",
|
|
"help_channel": "help"
|
|
},
|
|
"github": {
|
|
"token": os.getenv("GITHUB_TOKEN"),
|
|
"org": "aitbc",
|
|
"repo": "aitbc",
|
|
"team_slugs": ["core-team", "maintainers", "contributors"]
|
|
},
|
|
"email": {
|
|
"smtp_server": os.getenv("SMTP_SERVER"),
|
|
"smtp_port": 587,
|
|
"username": os.getenv("SMTP_USERNAME"),
|
|
"password": os.getenv("SMTP_PASSWORD"),
|
|
"from_address": "community@aitbc.dev"
|
|
},
|
|
"onboarding": {
|
|
"welcome_delay_hours": 1,
|
|
"follow_up_days": [3, 7, 14],
|
|
"resource_links": {
|
|
"documentation": "https://docs.aitbc.dev",
|
|
"api_reference": "https://api.aitbc.dev/docs",
|
|
"plugin_development": "https://docs.aitbc.dev/plugins",
|
|
"community_forum": "https://community.aitbc.dev",
|
|
"discord_invite": "https://discord.gg/aitbc"
|
|
}
|
|
}
|
|
}
|
|
|
|
config_file = Path(config_path)
|
|
if config_file.exists():
|
|
with open(config_file, 'r') as f:
|
|
user_config = json.load(f)
|
|
default_config.update(user_config)
|
|
|
|
return default_config
|
|
|
|
def _setup_logging(self) -> logging.Logger:
|
|
"""Setup logging for the onboarding system."""
|
|
logger = logging.getLogger("community_onboarding")
|
|
logger.setLevel(logging.INFO)
|
|
|
|
if not logger.handlers:
|
|
handler = logging.StreamHandler()
|
|
formatter = logging.Formatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
handler.setFormatter(formatter)
|
|
logger.addHandler(handler)
|
|
|
|
return logger
|
|
|
|
def _load_onboarding_data(self) -> Dict:
|
|
"""Load onboarding data from file."""
|
|
data_file = Path("data/onboarding_data.json")
|
|
if data_file.exists():
|
|
with open(data_file, 'r') as f:
|
|
return json.load(f)
|
|
return {"members": {}, "messages": {}, "follow_ups": {}}
|
|
|
|
def _save_onboarding_data(self) -> None:
|
|
"""Save onboarding data to file."""
|
|
data_file = Path("data/onboarding_data.json")
|
|
data_file.parent.mkdir(exist_ok=True)
|
|
with open(data_file, 'w') as f:
|
|
json.dump(self.onboarding_data, f, indent=2)
|
|
|
|
async def welcome_new_member(self, member_id: str, member_name: str,
|
|
platform: str = "discord") -> bool:
|
|
"""Welcome a new community member."""
|
|
try:
|
|
self.logger.info(f"Welcoming new member: {member_name} on {platform}")
|
|
|
|
# Create onboarding record
|
|
self.onboarding_data["members"][member_id] = {
|
|
"name": member_name,
|
|
"platform": platform,
|
|
"joined_at": datetime.now().isoformat(),
|
|
"welcome_sent": False,
|
|
"follow_ups_sent": [],
|
|
"resources_viewed": [],
|
|
"contributions": [],
|
|
"status": "new"
|
|
}
|
|
|
|
# Schedule welcome message
|
|
await self._schedule_welcome_message(member_id)
|
|
|
|
# Track member in analytics
|
|
await self._track_member_analytics(member_id, "joined")
|
|
|
|
self._save_onboarding_data()
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error welcoming member {member_name}: {e}")
|
|
return False
|
|
|
|
async def _schedule_welcome_message(self, member_id: str) -> None:
|
|
"""Schedule welcome message for new member."""
|
|
delay_hours = self.config["onboarding"]["welcome_delay_hours"]
|
|
|
|
# In production, this would use a proper task queue
|
|
# For now, we'll send immediately
|
|
await asyncio.sleep(delay_hours * 3600)
|
|
await self.send_welcome_message(member_id)
|
|
|
|
async def send_welcome_message(self, member_id: str) -> bool:
|
|
"""Send welcome message to member."""
|
|
try:
|
|
member_data = self.onboarding_data["members"][member_id]
|
|
platform = member_data["platform"]
|
|
|
|
if platform == "discord":
|
|
success = await self._send_discord_welcome(member_id)
|
|
elif platform == "github":
|
|
success = await self._send_github_welcome(member_id)
|
|
else:
|
|
self.logger.warning(f"Unsupported platform: {platform}")
|
|
return False
|
|
|
|
if success:
|
|
member_data["welcome_sent"] = True
|
|
member_data["welcome_sent_at"] = datetime.now().isoformat()
|
|
self._save_onboarding_data()
|
|
await self._track_member_analytics(member_id, "welcome_sent")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error sending welcome message to {member_id}: {e}")
|
|
return False
|
|
|
|
async def _send_discord_welcome(self, member_id: str) -> bool:
|
|
"""Send welcome message via Discord."""
|
|
try:
|
|
# Discord bot implementation would go here
|
|
# For now, we'll log the message
|
|
|
|
member_data = self.onboarding_data["members"][member_id]
|
|
welcome_message = self._generate_welcome_message(member_data["name"])
|
|
|
|
self.logger.info(f"Discord welcome message for {member_id}: {welcome_message}")
|
|
|
|
# In production:
|
|
# await discord_bot.send_message(
|
|
# channel_id=self.config["discord"]["welcome_channel"],
|
|
# content=welcome_message
|
|
# )
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error sending Discord welcome: {e}")
|
|
return False
|
|
|
|
async def _send_github_welcome(self, member_id: str) -> bool:
|
|
"""Send welcome message via GitHub."""
|
|
try:
|
|
# GitHub API implementation would go here
|
|
member_data = self.onboarding_data["members"][member_id]
|
|
welcome_message = self._generate_welcome_message(member_data["name"])
|
|
|
|
self.logger.info(f"GitHub welcome message for {member_id}: {welcome_message}")
|
|
|
|
# In production:
|
|
# await github_api.create_issue_comment(
|
|
# repo=self.config["github"]["repo"],
|
|
# issue_number=welcome_issue_number,
|
|
# body=welcome_message
|
|
# )
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error sending GitHub welcome: {e}")
|
|
return False
|
|
|
|
def _generate_welcome_message(self, member_name: str) -> str:
|
|
"""Generate personalized welcome message."""
|
|
resources = self.config["onboarding"]["resource_links"]
|
|
|
|
message = f"""🎉 Welcome to AITBC, {member_name}!
|
|
|
|
We're excited to have you join our community of developers, researchers, and innovators building the future of AI-powered blockchain technology.
|
|
|
|
🚀 **Quick Start Guide:**
|
|
1. **Documentation**: {resources["documentation"]}
|
|
2. **API Reference**: {resources["api_reference"]}
|
|
3. **Plugin Development**: {resources["plugin_development"]}
|
|
4. **Community Forum**: {resources["community_forum"]}
|
|
5. **Discord Chat**: {resources["discord_invite"]}
|
|
|
|
📋 **Next Steps:**
|
|
- ⭐ Star our repository on GitHub
|
|
- 📖 Read our contribution guidelines
|
|
- 💬 Introduce yourself in the #introductions channel
|
|
- 🔍 Check out our "good first issues" for newcomers
|
|
|
|
🛠️ **Ways to Contribute:**
|
|
- Code contributions (bug fixes, features)
|
|
- Documentation improvements
|
|
- Plugin development
|
|
- Community support and mentoring
|
|
- Testing and feedback
|
|
|
|
❓ **Need Help?**
|
|
- Ask questions in #help channel
|
|
- Check our FAQ at {resources["documentation"]}/faq
|
|
- Join our weekly office hours (Tuesdays 2PM UTC)
|
|
|
|
We're here to help you succeed! Don't hesitate to reach out.
|
|
|
|
Welcome aboard! 🚀
|
|
|
|
#AITBCCommunity #Welcome #OpenSource"""
|
|
|
|
return message
|
|
|
|
async def send_follow_up_message(self, member_id: str, day: int) -> bool:
|
|
"""Send follow-up message to member."""
|
|
try:
|
|
member_data = self.onboarding_data["members"][member_id]
|
|
|
|
if day in member_data["follow_ups_sent"]:
|
|
return True # Already sent
|
|
|
|
follow_up_message = self._generate_follow_up_message(member_data["name"], day)
|
|
|
|
if member_data["platform"] == "discord":
|
|
success = await self._send_discord_follow_up(member_id, follow_up_message)
|
|
else:
|
|
success = await self._send_email_follow_up(member_id, follow_up_message)
|
|
|
|
if success:
|
|
member_data["follow_ups_sent"].append(day)
|
|
member_data[f"follow_up_{day}_sent_at"] = datetime.now().isoformat()
|
|
self._save_onboarding_data()
|
|
await self._track_member_analytics(member_id, f"follow_up_{day}")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error sending follow-up to {member_id}: {e}")
|
|
return False
|
|
|
|
def _generate_follow_up_message(self, member_name: str, day: int) -> str:
|
|
"""Generate follow-up message based on day."""
|
|
resources = self.config["onboarding"]["resource_links"]
|
|
|
|
if day == 3:
|
|
return f"""Hi {member_name}! 👋
|
|
|
|
Hope you're settling in well! Here are some resources to help you get started:
|
|
|
|
🔧 **Development Setup:**
|
|
- Clone the repository: `git clone https://github.com/aitbc/aitbc`
|
|
- Install dependencies: `poetry install`
|
|
- Run tests: `pytest`
|
|
|
|
📚 **Learning Resources:**
|
|
- Architecture overview: {resources["documentation"]}/architecture
|
|
- Plugin tutorial: {resources["plugin_development"]}/tutorial
|
|
- API examples: {resources["api_reference"]}/examples
|
|
|
|
💬 **Community Engagement:**
|
|
- Join our weekly community call (Thursdays 3PM UTC)
|
|
- Share your progress in #show-and-tell
|
|
- Ask for help in #help
|
|
|
|
How's your experience been so far? Any questions or challenges we can help with?
|
|
|
|
#AITBCCommunity #Onboarding #GetStarted"""
|
|
|
|
elif day == 7:
|
|
return f"""Hi {member_name}! 🎯
|
|
|
|
You've been with us for a week! We'd love to hear about your experience:
|
|
|
|
📊 **Quick Check-in:**
|
|
- Have you been able to set up your development environment?
|
|
- Have you explored the codebase or documentation?
|
|
- Are there any areas where you'd like more guidance?
|
|
|
|
🚀 **Contribution Opportunities:**
|
|
- Good first issues: https://github.com/aitbc/aitbc/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
|
|
- Documentation improvements: {resources["documentation"]}/contribute
|
|
- Plugin ideas: {resources["plugin_development"]}/ideas
|
|
|
|
🎉 **Community Events:**
|
|
- Monthly hackathon (first Saturday)
|
|
- Plugin showcase (third Thursday)
|
|
- Office hours (every Tuesday 2PM UTC)
|
|
|
|
Your feedback helps us improve the onboarding experience. What would make your journey more successful?
|
|
|
|
#AITBCCommunity #Feedback #Community"""
|
|
|
|
elif day == 14:
|
|
return f"""Hi {member_name}! 🌟
|
|
|
|
Two weeks in - you're becoming part of the AITBC ecosystem!
|
|
|
|
🎯 **Next Level Engagement:**
|
|
- Consider joining a specialized team (security, plugins, docs, etc.)
|
|
- Start a plugin project: {resources["plugin_development"]}/starter
|
|
- Review a pull request to learn the codebase
|
|
- Share your ideas in #feature-requests
|
|
|
|
🏆 **Recognition Program:**
|
|
- Contributor of the month nominations
|
|
- Plugin contest participation
|
|
- Community spotlight features
|
|
- Speaking opportunities at community events
|
|
|
|
📈 **Your Impact:**
|
|
- Every contribution, no matter how small, helps
|
|
- Your questions help us improve documentation
|
|
- Your feedback shapes the project direction
|
|
- Your presence strengthens the community
|
|
|
|
What would you like to focus on next? We're here to support your journey!
|
|
|
|
#AITBCCommunity #Growth #Impact"""
|
|
|
|
else:
|
|
return f"Hi {member_name}! Just checking in. How's your AITBC journey going?"
|
|
|
|
async def _send_discord_follow_up(self, member_id: str, message: str) -> bool:
|
|
"""Send follow-up via Discord DM."""
|
|
try:
|
|
self.logger.info(f"Discord follow-up for {member_id}: {message[:100]}...")
|
|
# Discord DM implementation
|
|
return True
|
|
except Exception as e:
|
|
self.logger.error(f"Error sending Discord follow-up: {e}")
|
|
return False
|
|
|
|
async def _send_email_follow_up(self, member_id: str, message: str) -> bool:
|
|
"""Send follow-up via email."""
|
|
try:
|
|
self.logger.info(f"Email follow-up for {member_id}: {message[:100]}...")
|
|
# Email implementation
|
|
return True
|
|
except Exception as e:
|
|
self.logger.error(f"Error sending email follow-up: {e}")
|
|
return False
|
|
|
|
async def track_member_activity(self, member_id: str, activity_type: str,
|
|
details: Dict = None) -> None:
|
|
"""Track member activity for analytics."""
|
|
try:
|
|
if member_id not in self.onboarding_data["members"]:
|
|
return
|
|
|
|
member_data = self.onboarding_data["members"][member_id]
|
|
|
|
if "activities" not in member_data:
|
|
member_data["activities"] = []
|
|
|
|
activity = {
|
|
"type": activity_type,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"details": details or {}
|
|
}
|
|
|
|
member_data["activities"].append(activity)
|
|
|
|
# Update member status based on activity
|
|
if activity_type == "first_contribution":
|
|
member_data["status"] = "contributor"
|
|
elif activity_type == "first_plugin":
|
|
member_data["status"] = "plugin_developer"
|
|
|
|
self._save_onboarding_data()
|
|
await self._track_member_analytics(member_id, activity_type)
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error tracking activity for {member_id}: {e}")
|
|
|
|
async def _track_member_analytics(self, member_id: str, event: str) -> None:
|
|
"""Track analytics for member events."""
|
|
try:
|
|
# Analytics implementation would go here
|
|
self.logger.info(f"Analytics event: {member_id} - {event}")
|
|
|
|
# In production, send to analytics service
|
|
# await analytics_service.track_event({
|
|
# "member_id": member_id,
|
|
# "event": event,
|
|
# "timestamp": datetime.now().isoformat(),
|
|
# "properties": {}
|
|
# })
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error tracking analytics: {e}")
|
|
|
|
async def process_follow_ups(self) -> None:
|
|
"""Process scheduled follow-ups for all members."""
|
|
try:
|
|
current_date = datetime.now()
|
|
|
|
for member_id, member_data in self.onboarding_data["members"].items():
|
|
joined_date = datetime.fromisoformat(member_data["joined_at"])
|
|
|
|
for day in self.config["onboarding"]["follow_up_days"]:
|
|
follow_up_date = joined_date + timedelta(days=day)
|
|
|
|
if (current_date >= follow_up_date and
|
|
day not in member_data["follow_ups_sent"]):
|
|
await self.send_follow_up_message(member_id, day)
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error processing follow-ups: {e}")
|
|
|
|
async def generate_onboarding_report(self) -> Dict:
|
|
"""Generate onboarding analytics report."""
|
|
try:
|
|
total_members = len(self.onboarding_data["members"])
|
|
welcome_sent = sum(1 for m in self.onboarding_data["members"].values() if m.get("welcome_sent"))
|
|
|
|
status_counts = {}
|
|
for member in self.onboarding_data["members"].values():
|
|
status = member.get("status", "new")
|
|
status_counts[status] = status_counts.get(status, 0) + 1
|
|
|
|
platform_counts = {}
|
|
for member in self.onboarding_data["members"].values():
|
|
platform = member.get("platform", "unknown")
|
|
platform_counts[platform] = platform_counts.get(platform, 0) + 1
|
|
|
|
return {
|
|
"total_members": total_members,
|
|
"welcome_sent": welcome_sent,
|
|
"welcome_rate": welcome_sent / total_members if total_members > 0 else 0,
|
|
"status_distribution": status_counts,
|
|
"platform_distribution": platform_counts,
|
|
"generated_at": datetime.now().isoformat()
|
|
}
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error generating report: {e}")
|
|
return {}
|
|
|
|
async def run_daily_tasks(self) -> None:
|
|
"""Run daily onboarding tasks."""
|
|
try:
|
|
self.logger.info("Running daily onboarding tasks")
|
|
|
|
# Process follow-ups
|
|
await self.process_follow_ups()
|
|
|
|
# Generate daily report
|
|
report = await self.generate_onboarding_report()
|
|
self.logger.info(f"Daily onboarding report: {report}")
|
|
|
|
# Cleanup old data
|
|
await self._cleanup_old_data()
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error running daily tasks: {e}")
|
|
|
|
async def _cleanup_old_data(self) -> None:
|
|
"""Clean up old onboarding data."""
|
|
try:
|
|
cutoff_date = datetime.now() - timedelta(days=365)
|
|
|
|
# Remove members older than 1 year with no activity
|
|
to_remove = []
|
|
for member_id, member_data in self.onboarding_data["members"].items():
|
|
joined_date = datetime.fromisoformat(member_data["joined_at"])
|
|
|
|
if (joined_date < cutoff_date and
|
|
not member_data.get("activities") and
|
|
member_data.get("status") == "new"):
|
|
to_remove.append(member_id)
|
|
|
|
for member_id in to_remove:
|
|
del self.onboarding_data["members"][member_id]
|
|
self.logger.info(f"Removed inactive member: {member_id}")
|
|
|
|
if to_remove:
|
|
self._save_onboarding_data()
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error cleaning up data: {e}")
|
|
|
|
|
|
# CLI interface for the onboarding system
|
|
async def main():
|
|
"""Main CLI interface."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="AITBC Community Onboarding")
|
|
parser.add_argument("--welcome", help="Welcome new member (member_id,name,platform)")
|
|
parser.add_argument("--followup", help="Send follow-up (member_id,day)")
|
|
parser.add_argument("--report", action="store_true", help="Generate onboarding report")
|
|
parser.add_argument("--daily", action="store_true", help="Run daily tasks")
|
|
|
|
args = parser.parse_args()
|
|
|
|
onboarding = CommunityOnboarding()
|
|
|
|
if args.welcome:
|
|
member_id, name, platform = args.welcome.split(",")
|
|
await onboarding.welcome_new_member(member_id, name, platform)
|
|
print(f"Welcome message scheduled for {name}")
|
|
|
|
elif args.followup:
|
|
member_id, day = args.followup.split(",")
|
|
success = await onboarding.send_follow_up_message(member_id, int(day))
|
|
print(f"Follow-up sent: {success}")
|
|
|
|
elif args.report:
|
|
report = await onboarding.generate_onboarding_report()
|
|
print(json.dumps(report, indent=2))
|
|
|
|
elif args.daily:
|
|
await onboarding.run_daily_tasks()
|
|
print("Daily tasks completed")
|
|
|
|
else:
|
|
print("Use --help to see available options")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|