Files
aitbc/scripts/community_onboarding.py
oib f353e00172 chore(security): enhance environment configuration, CI workflows, and wallet daemon with security improvements
- Restructure .env.example with security-focused documentation, service-specific environment file references, and AWS Secrets Manager integration
- Update CLI tests workflow to single Python 3.13 version, add pytest-mock dependency, and consolidate test execution with coverage
- Add comprehensive security validation to package publishing workflow with manual approval gates, secret scanning, and release
2026-03-03 10:33:46 +01:00

560 lines
21 KiB
Python

#!/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())