multi-site-bot #82
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Multi-Site Bot — daily run for tarofortune + currency-map + geoinfomatic | |
| # | |
| # 매일 UTC 16:00 (KST 새벽 1시) + 0~2h 랜덤 지연으로 자동 게시. | |
| # 3사이트가 epoch_day 회전 — 매일 다른 사이트가 게시됨. | |
| # | |
| # 기존 daily-bot.yml과 공존 가능 (이 워크플로우가 더 새 기능 포함). | |
| # 운영자가 원하면 daily-bot.yml을 비활성화하고 이것만 사용. | |
| name: multi-site-bot | |
| on: | |
| schedule: | |
| # 매일 UTC 16:00 (KST 01:00) — 봇 게시 + KPI 수집 + 자동 콘텐츠 생성 한 번에 | |
| # 콘텐츠 생성 비용: OpenAI gpt-4o-mini 기준 약 ₩440/월 | |
| - cron: '0 16 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| force_site: | |
| description: '강제 사이트 (tarofortune / currency-map / geoinfomatic / zipscope / interview-copilot). 비우면 epoch_day 회전.' | |
| required: false | |
| type: choice | |
| options: ['', 'tarofortune', 'currency-map', 'geoinfomatic', 'zipscope', 'interview-copilot'] | |
| default: '' | |
| dry_run: | |
| description: '드라이런 (게시 안 함)' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_content_gen: | |
| description: '자동 콘텐츠 생성 강제 실행' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write # state 파일 자동 커밋용 | |
| issues: write # 일일 리포트 GitHub Issue 생성용 (없으면 403) | |
| jobs: | |
| run-bots: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 180 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Random delay 0~2h (anti-bot) | |
| if: github.event_name == 'schedule' | |
| run: | | |
| DELAY=$((RANDOM % 7200)) | |
| echo "Sleeping ${DELAY}s..." | |
| sleep $DELAY | |
| - name: Run multi-site orchestrator | |
| env: | |
| LEMMY_USERNAME: ${{ secrets.LEMMY_USERNAME }} | |
| LEMMY_PASSWORD: ${{ secrets.LEMMY_PASSWORD }} | |
| # 인스턴스·사이트 URL은 비밀이 아닌 설정값 → 하드코딩(secret 미설정 시 빈 값으로 'no host given' 버그 발생했었음) | |
| LEMMY_INSTANCE: lemmy.world | |
| MASTODON_INSTANCE: mastodon.social | |
| MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| INDEXNOW_KEY: ${{ secrets.INDEXNOW_KEY }} | |
| SITE_URL: https://tarofortune.pythonanywhere.com | |
| run: | | |
| ARGS="" | |
| if [ -n "${{ github.event.inputs.force_site }}" ]; then | |
| ARGS="$ARGS --site ${{ github.event.inputs.force_site }}" | |
| fi | |
| if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then | |
| ARGS="$ARGS --dry-run" | |
| fi | |
| if [ -f "./marketing/shared/multi_site_orchestrator.py" ]; then | |
| echo "▶ Using multi-site orchestrator (marketing/ inside repo)" | |
| python -X utf8 ./marketing/shared/multi_site_orchestrator.py $ARGS | |
| else | |
| echo "▶ marketing/ not found — falling back to legacy run_all_bots.py" | |
| python -X utf8 run_all_bots.py | |
| fi | |
| - name: Collect daily KPIs | |
| if: github.event.inputs.dry_run != 'true' | |
| env: | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| MASTODON_INSTANCE: mastodon.social | |
| LEMMY_INSTANCE: lemmy.world | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| if [ -f "./marketing/shared/analytics_collector.py" ]; then | |
| python -X utf8 ./marketing/shared/analytics_collector.py || echo "KPI collection failed (non-fatal)" | |
| fi | |
| - name: 📊 Daily report → GitHub Issue (운영자 알림) | |
| if: github.event.inputs.dry_run != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| PA_TOKEN: ${{ secrets.PA_TOKEN }} | |
| MASTODON_INSTANCE: mastodon.social | |
| LEMMY_INSTANCE: lemmy.world | |
| run: | | |
| if [ -f "./marketing/shared/daily_report.py" ]; then | |
| python -X utf8 ./marketing/shared/daily_report.py || echo "Daily report failed (non-fatal)" | |
| fi | |
| - name: 📧 Daily report → Email (geostaticss@gmail.com 직접 발송) | |
| if: github.event.inputs.dry_run != 'true' | |
| env: | |
| SMTP_HOST: ${{ secrets.SMTP_HOST }} | |
| SMTP_PORT: ${{ secrets.SMTP_PORT }} | |
| SMTP_USER: ${{ secrets.SMTP_USER }} | |
| SMTP_PASS: ${{ secrets.SMTP_PASS }} | |
| REPORT_TO: ${{ secrets.REPORT_TO }} | |
| REPORT_FROM: ${{ secrets.REPORT_FROM }} | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| PA_TOKEN: ${{ secrets.PA_TOKEN }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MASTODON_INSTANCE: mastodon.social | |
| LEMMY_INSTANCE: lemmy.world | |
| run: | | |
| if [ -z "$SMTP_USER" ] || [ -z "$SMTP_PASS" ]; then | |
| echo "SMTP_USER/SMTP_PASS not set — skipping email (GitHub Issue 알림은 작동 중)" | |
| exit 0 | |
| fi | |
| if [ -f "./marketing/shared/send_daily_report_email.py" ]; then | |
| python -X utf8 ./marketing/shared/send_daily_report_email.py || echo "Email send failed (non-fatal)" | |
| fi | |
| - name: Generate new content (daily) | |
| if: | | |
| github.event.inputs.dry_run != 'true' && | |
| (github.event_name == 'schedule' || github.event.inputs.run_content_gen == 'true') | |
| env: | |
| # 둘 중 하나만 박으면 작동 (OpenAI 우선, fallback Anthropic) | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| if [ -z "$OPENAI_API_KEY" ] && [ -z "$ANTHROPIC_API_KEY" ]; then | |
| echo "Neither OPENAI_API_KEY nor ANTHROPIC_API_KEY set — skipping" | |
| exit 0 | |
| fi | |
| if [ -f "./marketing/shared/auto_content_generator.py" ]; then | |
| # 매일 모든 사이트 × 영문+한국어 = 6개 콘텐츠/일 자동 생성 | |
| python -X utf8 ./marketing/shared/auto_content_generator.py || echo "content gen failed (non-fatal)" | |
| fi | |
| - name: Commit updated state files + new content | |
| if: github.event.inputs.dry_run != 'true' | |
| run: | | |
| git config user.name "multi-site-bot" | |
| git config user.email "bot@users.noreply.github.com" | |
| git add -A .lemmy_state.json .mastodon_state.json .devto_state.json \ | |
| .referral_log.jsonl .email_capture.jsonl \ | |
| marketing/shared/state \ | |
| marketing/shared/content_pools 2>/dev/null || true | |
| git diff --staged --quiet || git commit -m "chore(bot): state + KPI + content [skip ci]" | |
| git push || echo "Nothing to push or push failed (continuing)" | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Multi-site bot run" >> $GITHUB_STEP_SUMMARY | |
| echo "- Trigger: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Force site: ${{ github.event.inputs.force_site || '(auto rotate)' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Dry-run: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Content gen: ${{ github.event.inputs.run_content_gen || 'auto' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "./marketing/shared/state/daily_kpi/_latest.json" ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Latest KPI snapshot" >> $GITHUB_STEP_SUMMARY | |
| echo '```json' >> $GITHUB_STEP_SUMMARY | |
| head -c 2000 ./marketing/shared/state/daily_kpi/_latest.json >> $GITHUB_STEP_SUMMARY | |
| echo '' >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| fi |