|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Content Agent v0.5 — Content Idea Suggester |
| 4 | +Không viết content thay Thảo. |
| 5 | +Chỉ gợi ý ý tưởng dựa trên nội dung đã được duyệt trong repo. |
| 6 | +Thảo viết content — agent hỗ trợ research + gợi ý góc nhìn + track. |
| 7 | +
|
| 8 | +Flow: Scan repo → Gợi ý idea → Track |
| 9 | +""" |
| 10 | +import json, os, sys, requests |
| 11 | +from datetime import datetime, timedelta |
| 12 | + |
| 13 | +# === Config === |
| 14 | +_DEFAULT = { |
| 15 | + "content_dir": "~/.openclaw/workspace-main", |
| 16 | + "output_dir": "~/.openclaw/workspace-main/posts", |
| 17 | + "repo_path": "~/Developer/ai-growth-prompts", |
| 18 | + "repo_url": "https://github.com/thaolst/ai-growth-prompts", |
| 19 | + "telegram_chat_id": "1606915846" |
| 20 | +} |
| 21 | +_cfg_path = os.path.join(os.path.dirname(__file__), "config.json") |
| 22 | +_cfg_example = os.path.join(os.path.dirname(__file__), "config.example.json") |
| 23 | +try: |
| 24 | + with open(_cfg_path) as f: |
| 25 | + _user = json.load(f) |
| 26 | +except: |
| 27 | + try: |
| 28 | + with open(_cfg_example) as f: |
| 29 | + _user = {k: v for k, v in json.load(f).items() |
| 30 | + if not str(v).startswith("YOUR_") and not str(v).startswith("sk-")} |
| 31 | + except: |
| 32 | + _user = {} |
| 33 | +CONFIG = {k: os.path.expanduser(str(v)) if isinstance(v, str) and k not in ("telegram_token",) else v |
| 34 | + for k, v in {**_DEFAULT, **_user}.items()} |
| 35 | + |
| 36 | +class IdeaAgent: |
| 37 | + def __init__(self, config=None): |
| 38 | + self.cfg = {**CONFIG, **(config or {})} |
| 39 | + self.log_file = os.path.join(self.cfg["output_dir"], "social-performance-log.md") |
| 40 | + os.makedirs(self.cfg["output_dir"], exist_ok=True) |
| 41 | + |
| 42 | + # === SCAN REPO === |
| 43 | + def _scan_repo_topics(self) -> list: |
| 44 | + """Đọc các folder trong repo để lấy chủ đề đã duyệt""" |
| 45 | + repo = self.cfg.get("repo_path", "") |
| 46 | + if not os.path.exists(repo): |
| 47 | + return self._fallback_topics() |
| 48 | + |
| 49 | + topics = [] |
| 50 | + folder_map = { |
| 51 | + "00": "Campaign Level — S/M/L framework", |
| 52 | + "01": "Voucher Design — segment-based offers", |
| 53 | + "02": "Segment Analysis — targeting & intervention", |
| 54 | + "03": "Game Mechanics — engagement loops", |
| 55 | + "04": "Comm & Design Brief — communication planning", |
| 56 | + "05": "Automation Logic — design before coding", |
| 57 | + "06": "Retention Strategy — retention loops, point economy", |
| 58 | + "07": "Experiment Design — A/B testing framework", |
| 59 | + "08": "Growth Glossary — thuật ngữ growth", |
| 60 | + "09": "AI x Growth — AI prompts cho growth", |
| 61 | + "10": "n8n Growth Workflows — automation sẵn dùng", |
| 62 | + "11": "Content Agent — personal brand automation" |
| 63 | + } |
| 64 | + |
| 65 | + for folder in sorted(os.listdir(repo)): |
| 66 | + if folder[:2] in folder_map: |
| 67 | + readme = os.path.join(repo, folder, "README.md") |
| 68 | + desc = folder_map[folder[:2]] |
| 69 | + if os.path.exists(readme): |
| 70 | + with open(readme) as f: |
| 71 | + first_line = f.readline().strip().lstrip("# ") |
| 72 | + if first_line: |
| 73 | + topics.append({"prefix": folder[:2], "title": first_line, "desc": desc}) |
| 74 | + else: |
| 75 | + topics.append({"prefix": folder[:2], "title": f"Folder {folder[:2]}", "desc": desc}) |
| 76 | + else: |
| 77 | + topics.append({"prefix": folder[:2], "title": f"Folder {folder[:2]}", "desc": desc}) |
| 78 | + |
| 79 | + return topics |
| 80 | + |
| 81 | + def _fallback_topics(self): |
| 82 | + return [ |
| 83 | + {"prefix": "09", "title": "AI x Growth Marketing", "desc": "AI prompts cho growth"}, |
| 84 | + {"prefix": "10", "title": "n8n Growth Workflows", "desc": "automation sẵn dùng"}, |
| 85 | + {"prefix": "11", "title": "Content Agent", "desc": "personal brand automation"} |
| 86 | + ] |
| 87 | + |
| 88 | + # === RESEARCH — gợi ý idea === |
| 89 | + def suggest_ideas(self, count=3) -> list: |
| 90 | + """Quét repo → gợi ý ý tưởng content từ nội dung đã duyệt""" |
| 91 | + topics = self._scan_repo_topics() |
| 92 | + import random |
| 93 | + random.shuffle(topics) |
| 94 | + |
| 95 | + ideas = [] |
| 96 | + for i, t in enumerate(topics[:count]): |
| 97 | + # Gợi ý góc nhìn khác nhau, không viết content |
| 98 | + angles = { |
| 99 | + "Campaign Level": ["Khi nào nên chọn mức S, khi nào M, khi nào L?", "Ước lượng campaign level trước khi làm — tiết kiệm bao nhiêu thời gian?"], |
| 100 | + "Voucher": ["Voucher cho new user khác gì voucher cho dormant?", "Thiết kế offer theo segment — không phải ai cũng thích giảm giá"], |
| 101 | + "Segment": ["Dormant 30 ngày vs 60 ngày — can thiệp khác nhau thế nào?", "Phân nhóm user trước khi launch campaign"], |
| 102 | + "Game": ["Cơ chế game nào giữ chân user lâu nhất?", "Streak, spin wheel, hay challenge — cái nào phù hợp campaign của bạn?"], |
| 103 | + "Retention": ["Retention không phải là engagement", "Xu economy — vì sao điểm thưởng giữ chân user tốt hơn giảm giá"], |
| 104 | + "Experiment": ["A/B test bao nhiêu sample là đủ?", "Cách đọc kết quả test — statistical significance cho marketer"], |
| 105 | + "Glossary": ["RAG, TF-IDF, Embedding — marketer có cần hiểu không?", "Thuật ngữ AI cơ bản cho growth team"], |
| 106 | + "AI x Growth": ["Prompt nào cho campaign nào?", "AI agents có thực sự cần thiết cho growth team 2026?"], |
| 107 | + "n8n": ["Tự động hoá campaign monitoring với n8n", "Workflow nào tiết kiệm nhiều thời gian nhất?"], |
| 108 | + "Content Agent": ["Cách mình dùng AI để gợi ý content idea", "Research → Queue → Draft → Track: luồng content cho personal brand"] |
| 109 | + } |
| 110 | + |
| 111 | + # Tìm angle phù hợp |
| 112 | + suggested_angle = "Chia sẻ kinh nghiệm thực tế từ repo" |
| 113 | + for key, vals in angles.items(): |
| 114 | + if key.lower() in t["desc"].lower() or key.lower() in t["title"].lower(): |
| 115 | + suggested_angle = random.choice(vals) |
| 116 | + break |
| 117 | + |
| 118 | + # Hook idea — chỉ là gợi ý cấu trúc, Thảo tự viết |
| 119 | + hook_ideas = [ |
| 120 | + f"Chủ đề: {t['title']}. Góc nhìn: chia sẻ kinh nghiệm thực tế từ repo.", |
| 121 | + f"Chủ đề: {t['title']}. Góc nhìn: áp dụng vào campaign thực tế khác gì lý thuyết.", |
| 122 | + f"Chủ đề: {t['title']}. Góc nhìn: 3 điều rút ra sau khi áp dụng." |
| 123 | + ] |
| 124 | + |
| 125 | + ideas.append({ |
| 126 | + "source_folder": f"{t['prefix']}-{t['title'].split('—')[0].strip().lower().replace(' ', '-')[:20]}", |
| 127 | + "topic": t["title"], |
| 128 | + "hook_ideas": hook_ideas, |
| 129 | + "suggested_angle": suggested_angle, |
| 130 | + "why_now": f"Folder {t['prefix']} đã được duyệt và public trong repo" |
| 131 | + }) |
| 132 | + |
| 133 | + return ideas |
| 134 | + |
| 135 | + # === GỬI TELEGRAM === |
| 136 | + def _telegram(self, text: str): |
| 137 | + """Gửi gợi ý content qua Telegram""" |
| 138 | + token = self.cfg.get("telegram_token", "") |
| 139 | + cid = self.cfg.get("telegram_chat_id", "") |
| 140 | + if token and cid: |
| 141 | + requests.post(f"https://api.telegram.org/bot{token}/sendMessage", |
| 142 | + data={"chat_id": cid, "text": text, "parse_mode": "HTML"}) |
| 143 | + |
| 144 | + # === TRACK === |
| 145 | + def track(self, topic: str, platform: str): |
| 146 | + """Ghi log bài đã đăng — Thảo tự fill metrics sau""" |
| 147 | + entry = f"\n{datetime.now().strftime('%Y-%m-%d')} | {topic[:40]:40s} | {platform:10s} | posted | ? views | ? likes | ? comments" |
| 148 | + if not os.path.exists(self.log_file): |
| 149 | + with open(self.log_file, "w") as f: |
| 150 | + f.write("date | topic | platform | status | views | likes | comments") |
| 151 | + with open(self.log_file, "a") as f: |
| 152 | + f.write(entry) |
| 153 | + |
| 154 | + # === RUN === |
| 155 | + def run(self, mode="suggest"): |
| 156 | + if mode == "suggest": |
| 157 | + ideas = self.suggest_ideas(3) |
| 158 | + msg_lines = ["💡 GỢI Ý CONTENT IDEA — hôm nay"] |
| 159 | + msg_lines.append(f"Dựa trên nội dung đã duyệt trong repo\n") |
| 160 | + |
| 161 | + for i, idea in enumerate(ideas, 1): |
| 162 | + msg_lines.append(f"#{i} — {idea['topic']}") |
| 163 | + msg_lines.append(f" Góc nhìn: {idea['suggested_angle']}") |
| 164 | + msg_lines.append(f" Hook idea:") |
| 165 | + for h in idea['hook_ideas']: |
| 166 | + msg_lines.append(f" • {h}") |
| 167 | + msg_lines.append(f" 📁 {idea['source_folder']}") |
| 168 | + msg_lines.append("") |
| 169 | + |
| 170 | + msg = "\n".join(msg_lines) |
| 171 | + print(msg) |
| 172 | + self._telegram(msg) |
| 173 | + print("\n📁 Log: track performance tại", self.log_file) |
| 174 | + |
| 175 | + if mode == "track": |
| 176 | + topic = input("Topic đã đăng: ") |
| 177 | + platform = input("Platform: ") |
| 178 | + self.track(topic, platform) |
| 179 | + print("✅ Tracked") |
| 180 | + |
| 181 | + |
| 182 | +if __name__ == "__main__": |
| 183 | + mode = sys.argv[1] if len(sys.argv) > 1 else "suggest" |
| 184 | + IdeaAgent().run(mode) |
0 commit comments