Skip to content

multi-site-bot

multi-site-bot #74

# 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