Skip to content

Add social media automation kit for daily Threads posting - #4

Merged
ktfth merged 8 commits into
mainfrom
claude/festive-fermat-0gZeA
May 24, 2026
Merged

Add social media automation kit for daily Threads posting#4
ktfth merged 8 commits into
mainfrom
claude/festive-fermat-0gZeA

Conversation

@ktfth

@ktfth ktfth commented May 24, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a complete cross-platform social media automation kit for publishing daily Kambo posts to Threads. Includes Python-based poster with dual publishing modes (browser automation and official API), scheduling support for Windows/Linux/macOS, and 30 days of pre-written content across Twitter, LinkedIn, and Instagram.

Key Changes

  • poster.py — Main automation script with:

    • Cycle-based day calculation (30-day repeating schedule)
    • Markdown post extraction with repository link injection
    • Dual publishing modes: browser (Chrome with Playwright) and API (Threads Graph API)
    • JSONL-based post logging with timestamps and platform labels
    • CLI commands: today, day N, list, setup, schedule install/uninstall
    • Cross-platform scheduling integration (Windows Task Scheduler, Linux/macOS cron)
  • threads_client.py — Threads publishing client with:

    • ThreadsAPIClient for official Threads Graph API (text posts, container management, polling)
    • post_via_browser() for Chrome automation via Playwright (no API key required, uses existing session)
    • OS-specific Chrome profile detection (Windows, macOS, Linux)
    • Character limit enforcement (500 chars for Threads)
    • Screenshot capture on browser mode for verification
  • schedule.sh — Bash scheduler for Linux/macOS with:

    • Day-of-cycle calculation matching Python implementation
    • Cron job installation/uninstallation
    • Post display with platform emoji labels
    • Calendar and post listing commands
  • setup-windows.ps1 — Windows setup automation:

    • Python 3.11+ verification
    • Chrome detection
    • Dependency installation (playwright, httpx, python-dotenv)
    • Interactive .env configuration
    • Task Scheduler integration with XML template
  • 30 days of pre-written content (posts/ directory):

    • Twitter posts (280 chars, technical focus)
    • LinkedIn posts (professional, long-form)
    • Instagram captions (visual-first)
    • Covers: launch, problem, solution, architecture, installation, phases (recon/scanning/vulns/exploitation), evidence chains, cloud security, scope management, CVSS, metrics, self-improvement, calibration, CTF mode, tools, contributions, roadmap, tips (SSRF, subdomain takeover), reports, workflow
  • Configuration & Documentation:

    • .env.example with all configurable options (POSTER_MODE, CHROME_PROFILE_DIR, THREADS credentials, KAMBO_START_DATE)
    • README.md with setup instructions for all platforms, mode explanations, and usage examples
    • calendar-30days.md with content calendar and themes
    • hashtags.md with platform-specific hashtag banks
    • windows-task.xml for manual Task Scheduler import
    • requirements-social.txt with Python dependencies
  • Claude Code Integration (.claude/skills/kambo-social/SKILL.md):

    • Skill definition for /kambo-social trigger
    • Step-by-step instructions for day calculation, post loading, and browser automation
    • Integration with post-log checking to prevent duplicate daily posts
  • Project Integration:

    • Updated .gitignore to exclude .start-date, .env, post-log.jsonl, and debug screenshots
    • Updated CLAUDE.md to reference the new social media skill

Notable Implementation Details

  • Cycle Management: Uses .start-date file or KAMBO_START_DATE env var to track cycle start; calculates current day as (today - start_date).days % 30 + 1
  • Markdown Parsing: Extracts first code block from markdown files as post text; automatically appends repository link if missing
  • Dual Publishing: Browser mode requires no API credentials (uses Chrome session); API mode requires THREADS_USER_ID and THREADS_ACCESS_TOKEN
  • Cross-Platform: Detects OS and uses appropriate Chrome profile paths; scheduling via native tools (Task Scheduler on Windows, cron on

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA

Summary by Sourcery

Add a cross-platform social media automation kit for posting daily Kambo content to Threads, including scheduling, posting clients, and pre-written multi-platform content.

New Features:

  • Introduce a Python-based poster CLI that selects the daily post from a 30-day cycle, extracts markdown content, and publishes to Threads with optional dry-run and logging.
  • Add a Threads client supporting both official Threads Graph API and browser automation via Playwright with OS-specific Chrome profile handling and character limit enforcement.
  • Provide cross-platform scheduling via a Bash cron helper for Linux/macOS and PowerShell/Task Scheduler setup for Windows to automate daily posting.
  • Ship a curated 30-day library of pre-written posts for Twitter, LinkedIn, and Instagram, plus a content calendar and hashtag bank to guide social promotion.
  • Expose a new Claude Code skill (/kambo-social) to generate and publish the daily Kambo post through browser automation.

Enhancements:

  • Document the social media workflow, configuration, and setup steps for all platforms in a dedicated README and supporting config/templates.
  • Extend project docs to reference the new social posting capability and ignore social-media environment, state, log, and debug files in version control.

Documentation:

  • Add comprehensive social-media README, calendar, hashtags guide, and Instagram visual suggestions to document the 30-day campaign and automation usage.

Chores:

  • Update CLAUDE skill registry and .gitignore to integrate and isolate the new social-media automation assets.

claude added 4 commits May 24, 2026 18:54
- 30 posts prontos para Twitter/X, LinkedIn e Instagram
- Calendário de conteúdo com 4 semanas temáticas (apresentação,
  features, workflows, comunidade)
- Banco de hashtags por plataforma
- Script schedule.sh com agendamento cron às 18h diário:
    schedule.sh install  → cron job 18h todo dia
    schedule.sh today    → post do dia
    schedule.sh day N    → post do dia N
    schedule.sh list     → lista todos os posts
- Ciclo automático de 30 dias com log de publicações

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA
- poster.py: publicador cross-platform (Windows/macOS/Linux)
  - 'today', 'day N', 'list', 'setup', 'schedule install/uninstall/status'
  - Ciclo automático de 30 dias baseado na data de início
  - Log JSONL de cada publicação
  - dry-run mode para visualizar sem publicar

- threads_client.py: dois modos de publicação
  - Modo 'api': API Graph oficial do Threads (sem browser)
  - Modo 'browser': Playwright abre Chrome com perfil do usuário,
    detecta sessão logada, posta via automação, screenshot de confirmação
  - Modo 'auto': tenta API primeiro, cai para browser como fallback

- setup-windows.ps1: setup interativo para Windows
  - Verifica Python 3.11+ e Google Chrome
  - Instala dependências + playwright install chrome
  - Auto-detecta caminho do perfil Chrome do usuário
  - Instala Task Scheduler (18h diário) via Register-ScheduledTask

- windows-task.xml: XML de importação manual para Task Scheduler
- requirements-social.txt: httpx, playwright, python-dotenv, mistune
- .env.example: template de configuração comentado
- .gitignore: exclui .env, .start-date, screenshots, logs

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA
Skill /kambo-social:
- Calcula o dia do ciclo de 30 dias (lê .start-date, cria se não existir)
- Carrega o post correspondente de social-media/posts/
- Extrai o texto do primeiro bloco de código do markdown
- Usa ferramentas playwright_* do browser MCP para:
    → navegar para threads.net
    → verificar login (para se não estiver logado)
    → abrir o composer, injetar texto via JS (preserva emojis)
    → clicar em Publicar
    → tirar screenshot de confirmação
- Registra cada publicação em social-media/post-log.jsonl
- Suporta 'publica o dia N' para forçar um dia específico

Requer @playwright/mcp configurado no .mcp.json (ver README)

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA
Remove referência ao @playwright/mcp externo.
A skill agora usa as ferramentas de browser integradas ao Claude Code,
disponíveis quando iniciado com 'claude --chrome'.

Também adiciona /kambo-social à tabela de roteamento do CLAUDE.md.

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA
@sourcery-ai

sourcery-ai Bot commented May 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a cross-platform social media automation kit under social-media/ to publish a 30‑day rotating series of Kambo marketing posts to Threads, including a Python poster CLI with scheduling, a dual-mode Threads client (API or browser via Playwright/Chrome), OS-specific schedulers and setup scripts, pre-written post content, documentation, and a new Claude Code skill wiring this flow into the /kambo-social trigger.

Sequence diagram for posting a daily Kambo message to Threads

sequenceDiagram
    actor User
    participant PosterCLI as poster.py
    participant ThreadsClient as publish_to_threads
    participant ThreadsAPI as ThreadsAPIClient
    participant BrowserMode as post_via_browser

    User->>PosterCLI: main(today | day N)
    PosterCLI->>PosterCLI: get_cycle_day / POST_MAP lookup
    PosterCLI->>PosterCLI: extract_post_text
    PosterCLI->>ThreadsClient: publish_to_threads(text)

    alt POSTER_MODE=api or auto with API creds
        ThreadsClient->>ThreadsAPI: post(text)
        ThreadsAPI-->>ThreadsClient: {success, post_id, url, mode=api}
    else POSTER_MODE=browser or API unavailable
        ThreadsClient->>BrowserMode: post_via_browser(text)
        BrowserMode-->>ThreadsClient: {success, url=threads.net, mode=browser, screenshot}
    end

    ThreadsClient-->>PosterCLI: result
    PosterCLI->>PosterCLI: log_post(day, post_path, result)
    PosterCLI-->>User: print status + URL/screenshot
Loading

File-Level Changes

Change Details Files
Introduce poster CLI that maps a 30-day content cycle to markdown posts, extracts post text, publishes to Threads via threads_client, logs outcomes, and manages cross-platform scheduling and interactive setup.
  • Define POST_MAP and utility functions to compute the current cycle day using .start-date or KAMBO_START_DATE and to extract the first code block from markdown posts, appending a repo link if missing.
  • Implement async publish_day() which loads the mapped post, prints a preview with character counts, and calls publish_to_threads(), writing JSONL log entries per post.
  • Add interactive run_setup() that copies .env.example to .env, asks for POSTER_MODE and optional Threads API credentials, and prints next steps for installing Python and Playwright dependencies.
  • Provide schedule_* helpers for Windows (schtasks) and Unix (cron) to install/uninstall a daily 18:00 job and a schedule_status() command that shows scheduler state and last posts, wired into an argparse CLI with subcommands today, day, list, setup, and schedule.
social-media/poster.py
Add Threads client that supports official Threads Graph API posting as well as browser-based posting via Playwright using an existing Chrome profile, with OS-specific profile resolution and character-limit enforcement.
  • Create ThreadsAPIClient dataclass using httpx.AsyncClient to create a text thread container, poll its status, and publish it via Threads Graph API, returning IDs and canonical URLs.
  • Implement post_via_browser() that launches Chrome through Playwright with a persistent user data dir, navigates to threads.net, verifies login, opens the composer, injects post text into the contenteditable field, clicks publish, and captures debug/confirmation screenshots.
  • Provide publish_to_threads() wrapper that chooses mode based on POSTER_MODE or explicit argument, preferring API when valid credentials are present and falling back to browser mode when necessary.
  • Expose a small CLI in threads_client.py that posts arbitrary text via the chosen mode and prints publication metadata.
social-media/threads_client.py
Introduce a Bash-based scheduler script for Linux/macOS that mirrors the 30-day cycle logic, prints posts to stdout, and integrates with cron.
  • Define a bash POST_MAP and get_day_number() that uses KAMBO_START_DATE or a .start-date file and Python or date arithmetic to compute day=((today-start_date)%30)+1.
  • Implement show_post(), list_posts(), and logging to a plain-text post-log.txt including date, day, platform, and file path.
  • Add install_cron(), uninstall_cron(), and show_status() functions that manage a daily 18:00 cron entry and report status plus recent log lines.
  • Provide a simple CLI interface with commands today, day N, list, install, uninstall, status, and help.
social-media/schedule.sh
Add Windows-specific PowerShell setup script and Task Scheduler template to automate environment configuration and scheduling for the poster.
  • Implement setup-windows.ps1 to detect a suitable Python 3.11+ executable and Chrome binary, install requirements-social.txt and Playwright Chrome, create/update .env from .env.example, inject CHROME_PROFILE_DIR, optionally create a daily 18:00 scheduled task that runs poster.py today, and offer a dry-run test.
  • Provide windows-task.xml Task Scheduler definition that can be manually edited (Python path, project path, user) and imported to schedule python poster.py today at 18:00 daily with appropriate execution settings.
social-media/setup-windows.ps1
social-media/windows-task.xml
Bundle and organize 30 days of cross-platform social media content plus auxiliary docs (calendar and hashtags) to support the automation.
  • Create social-media/posts/ subdirectories (twitter, linkedin, instagram) containing markdown files for each day that include platform-specific copy and, where relevant, thread continuations or visual suggestions inside fenced code blocks.
  • Add calendar-30days.md that documents the 30-day content plan, mapping each day to a theme, file path, and suggested platform, including guidance for repeating the cycle.
  • Add hashtags.md providing curated hashtag sets for Twitter, LinkedIn, Instagram, and YouTube, intended to be used when crafting or extending posts.
social-media/posts/twitter/dia-01-lancamento.md
social-media/posts/twitter/dia-02-problema.md
social-media/posts/twitter/dia-04-arquitetura.md
social-media/posts/twitter/dia-05-instalacao.md
social-media/posts/twitter/dia-07-recap1.md
social-media/posts/twitter/dia-08-recon.md
social-media/posts/twitter/dia-09-scanning.md
social-media/posts/twitter/dia-11-evidence.md
social-media/posts/twitter/dia-13-cloud.md
social-media/posts/twitter/dia-16-scope.md
social-media/posts/twitter/dia-17-cvss.md
social-media/posts/twitter/dia-19-calibration.md
social-media/posts/twitter/dia-20-postexploit.md
social-media/posts/twitter/dia-23-tools.md
social-media/posts/twitter/dia-24-ctf.md
social-media/posts/twitter/dia-25-metrics.md
social-media/posts/twitter/dia-27-tip-ssrf.md
social-media/posts/twitter/dia-28-tip-takeover.md
social-media/posts/twitter/dia-30-cta.md
social-media/posts/linkedin/dia-03-solucao.md
social-media/posts/linkedin/dia-06-claudecode.md
social-media/posts/linkedin/dia-10-vulns.md
social-media/posts/linkedin/dia-12-api.md
social-media/posts/linkedin/dia-15-workflow.md
social-media/posts/linkedin/dia-18-selfimprove.md
social-media/posts/linkedin/dia-22-contribuir.md
social-media/posts/linkedin/dia-26-report.md
social-media/posts/linkedin/dia-29-roadmap.md
social-media/posts/instagram/dia-14-recap2.md
social-media/posts/instagram/dia-21-recap3.md
social-media/calendar-30days.md
social-media/hashtags.md
Add social-media documentation, environment template, and Python dependency list to support installation and usage across platforms.
  • Create social-media/README.md that describes directory structure, setup instructions for Windows/macOS/Linux, .env configuration, publishing modes, scheduling, and logging, with example commands.
  • Add .env.example capturing configuration knobs like POSTER_MODE, CHROME_PROFILE_DIR/NAME, CHROME_HEADLESS, THREADS_USER_ID/THREADS_ACCESS_TOKEN, and KAMBO_START_DATE, referenced by setup scripts and poster.py.
  • Introduce requirements-social.txt enumerating Python dependencies for posting (Playwright, httpx, python-dotenv, etc.).
social-media/README.md
social-media/.env.example
social-media/requirements-social.txt
Integrate the new social media flow into Claude Code as a skill and update project metadata/gitignore to support new artifacts.
  • Add .claude/skills/kambo-social/SKILL.md that specifies the /kambo-social skill behavior: computing the 30-day cycle day, mapping to the correct post file, parsing the first code block as post text, controlling Chrome to publish via threads.net, logging to post-log.jsonl, and handling idempotency when a post already exists for today.
  • Update CLAUDE.md skills table to include the new /kambo-social trigger for social posting-related prompts.
  • Extend .gitignore to exclude social-media/.start-date, social-media/.env, social-media/post-log.jsonl, and debug screenshots created by Threads automation.
.claude/skills/kambo-social/SKILL.md
CLAUDE.md
.gitignore

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

Copy link
Copy Markdown

Regression validator

Category Count
new 572

No regressions or failures detected.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 security issue, 11 other issues, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • The 30‑day post mapping is currently duplicated in poster.py, schedule.sh and .claude/skills/kambo-social/SKILL.md, which will easily drift over time; consider centralizing this mapping (e.g. in a shared JSON/YAML file) and loading it in each consumer instead of hard‑coding it three times.
  • In schedule.sh, the date fallback uses date -d which is not available on macOS (BSD date), so the cycle‑day calculation will break on that platform; it would be safer to rely on a single, portable implementation (e.g. always calling python3 or using a small helper script) instead of shell date arithmetic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The 30‑day post mapping is currently duplicated in `poster.py`, `schedule.sh` and `.claude/skills/kambo-social/SKILL.md`, which will easily drift over time; consider centralizing this mapping (e.g. in a shared JSON/YAML file) and loading it in each consumer instead of hard‑coding it three times.
- In `schedule.sh`, the date fallback uses `date -d` which is not available on macOS (BSD `date`), so the cycle‑day calculation will break on that platform; it would be safer to rely on a single, portable implementation (e.g. always calling `python3` or using a small helper script) instead of shell date arithmetic.

## Individual Comments

### Comment 1
<location path="social-media/poster.py" line_range="121" />
<code_context>
+    content = markdown_path.read_text(encoding="utf-8")
+
+    # Procura blocos de código: ```\n...\n```
+    pattern = re.compile(r"```\n(.*?)\n```", re.DOTALL)
+    matches = pattern.findall(content)
+
</code_context>
<issue_to_address>
**issue:** Markdown code fence extraction is brittle for language-tagged or CRLF code blocks.

The regex only matches fences of the form ```\n...\n``` and will miss common variants like ```txt or CRLF-terminated lines, causing `extract_post_text` to fail on valid Markdown. Please make the pattern more flexible (e.g., allow an optional language tag after ``` and support `\r?\n`), or consider using a Markdown parser to get the first fenced code block instead of relying on a brittle regex.
</issue_to_address>

### Comment 2
<location path="social-media/poster.py" line_range="382-384" />
<code_context>
+        print("ℹ️  Nenhum crontab encontrado.")
+        return
+
+    lines = [
+        l for l in result.stdout.splitlines()
+        if "KamboSocialPoster" not in l and "poster.py" not in l
+    ]
+    new_crontab = "\n".join(lines) + "\n"
</code_context>
<issue_to_address>
**issue (bug_risk):** Cron uninstall will delete any crontab line mentioning `poster.py`, not just this tool's job.

In `schedule_unix_uninstall`, this filter removes any crontab line containing `poster.py`, which could delete unrelated jobs that happen to call a poster script. Since the installed entry is already tagged with `# KamboSocialPoster`, please match on that marker (and ideally the full command) instead of the generic filename.
</issue_to_address>

### Comment 3
<location path="social-media/threads_client.py" line_range="244" />
<code_context>
+
+            if not text_area:
+                # Screenshot para debug
+                screenshot_path = Path(__file__).parent / "debug-screenshot.png"
+                await page.screenshot(path=str(screenshot_path))
+                raise RuntimeError(
</code_context>
<issue_to_address>
**suggestion:** Debug screenshots are always written to the same filename, potentially losing prior diagnostics.

This file is overwritten on each failure, which can make diagnosing intermittent issues difficult across runs. Consider including a timestamp or UUID in the filename (as you do for success screenshots) so multiple failure snapshots are preserved.

Suggested implementation:

```python
from pathlib import Path
from datetime import datetime

```

```python
            if not text_area:
                # Screenshot para debug (usar nome único para não sobrescrever)
                timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
                screenshot_path = Path(__file__).parent / f"debug-screenshot-{timestamp}.png"
                await page.screenshot(path=str(screenshot_path))
                raise RuntimeError(

```

If `from pathlib import Path` is not present in this file, you will also need to add it near the other imports.  
Optionally, you may want to include `str(screenshot_path)` in the `RuntimeError` message body so the caller can easily locate the saved debug screenshot.
</issue_to_address>

### Comment 4
<location path="social-media/schedule.sh" line_range="84-93" />
<code_context>
+  # Se o arquivo .start-date existir, usa como referência
+  if [[ -f "$SCRIPT_DIR/.start-date" ]]; then
+    START_DATE=$(cat "$SCRIPT_DIR/.start-date")
+  else
+    # Primeira execução: salva a data de início
+    echo "$START_DATE" > "$SCRIPT_DIR/.start-date"
+  fi
+
+  TODAY=$(date +%Y-%m-%d)
+
+  # Calcula diferença em dias
+  if command -v python3 &>/dev/null; then
+    DIFF=$(python3 -c "from datetime import date; print((date.fromisoformat('$TODAY') - date.fromisoformat('$START_DATE')).days)")
+  else
+    # Fallback: usa date aritmética do sistema
+    DIFF=$(( ($(date -d "$TODAY" +%s) - $(date -d "$START_DATE" +%s)) / 86400 ))
+  fi
+
</code_context>
<issue_to_address>
**issue (bug_risk):** GNU `date -d` is not portable to macOS, which can break the day calculation.

This fallback still relies on GNU `date -d`, so it will fail on macOS’s BSD `date`. Since this is meant to run on Linux/macOS, consider a more portable approach: for example, always use `python3` when available and otherwise either hard-fail with a clear message or add a macOS-specific branch using `date -j -f`. As is, macOS users without `python3` will hit a runtime error here.
</issue_to_address>

### Comment 5
<location path="social-media/setup-windows.ps1" line_range="132-135" />
<code_context>
+    Write-Host "✅ Perfil encontrado: $chromeProfileDir" -ForegroundColor Green
+
+    # Atualiza .env com o caminho do perfil
+    $envContent = Get-Content $envFile -Raw
+    if ($envContent -match "CHROME_PROFILE_DIR=`$") {
+        $envContent = $envContent -replace "CHROME_PROFILE_DIR=", "CHROME_PROFILE_DIR=$chromeProfileDir"
+        Set-Content $envFile $envContent -NoNewline
+        Write-Host "   Caminho adicionado ao .env automaticamente." -ForegroundColor Green
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** `.env` patching for `CHROME_PROFILE_DIR` is fragile and may not match the actual template format.

The pattern only matches a line ending with `CHROME_PROFILE_DIR=` and then performs a blind `-replace` across the whole file, which risks altering other occurrences unintentionally. `Set-Content -NoNewline` also removes the trailing newline, which can break some tools. Consider processing the file line-by-line so only the `CHROME_PROFILE_DIR` entry is updated, appending it if absent, and keeping the final newline intact.
</issue_to_address>

### Comment 6
<location path="social-media/posts/linkedin/dia-18-selfimprove.md" line_range="20" />
<code_context>
+• Precisão dos findings
+• Padrão de uso ao longo do tempo
+
+Isso cria um histórico objetividade de desempenho por ferramenta.
+
+━━━━━━━━━━━━━━━━━━━━━━━━
</code_context>
<issue_to_address>
**issue (typo):** Ajustar para "histórico objetivo" para corrigir a construção gramatical.

Use "Isso cria um histórico objetivo de desempenho por ferramenta.", pois "histórico objetividade" é uma construção gramaticalmente incorreta em português.
</issue_to_address>

### Comment 7
<location path="social-media/posts/twitter/dia-11-evidence.md" line_range="51" />
<code_context>
+```
+
+```
+O model de confiança é configurável.
+
+Você pode calibrar os thresholds via /kambo-calibrate baseado no seu histórico de reports aceitos/rejeitados.
</code_context>
<issue_to_address>
**issue (typo):** Trocar "model" por "modelo" para manter o português correto.

Como o restante do parágrafo está em português, manter "modelo" aqui preserva a consistência do texto.

```suggestion
O modelo de confiança é configurável.
```
</issue_to_address>

### Comment 8
<location path="social-media/posts/twitter/dia-02-problema.md" line_range="40" />
<code_context>
+2. "faça um bug bounty completo"
+3. ☕ beba seu café
+
+O Claude executa as 5 fases, gradu os findings e gera o template HackerOne/Bugcrowd automaticamente.
+```
+
</code_context>
<issue_to_address>
**issue (typo):** Corrigir "gradu" para "gradua" no verbo.

Faltou uma letra no verbo: use "gradua" em vez de "gradu" para manter a frase gramaticalmente correta.

Suggested implementation:

```
O Claude executa as 5 fases, gradua os findings e gera o template HackerOne/Bugcrowd automaticamente.

```

Recomendo fazer uma busca rápida por "gradu " no restante do repositório para garantir que não existam outras ocorrências com a mesma grafia incorreta que também precisem ser corrigidas.
</issue_to_address>

### Comment 9
<location path="social-media/posts/linkedin/dia-12-api.md" line_range="37" />
<code_context>
+
+🔗 github.com/ktfth/kambo
+
+#APISecuriy #OWASP #BugBounty #CyberSecurity #BOLA #BFLA #PenetrationTesting #WebSecurity #EthicalHacking
+```
</code_context>
<issue_to_address>
**issue (typo):** Corrigir a hashtag para "#APISecurity".

Isso evita o erro de digitação atual (#APISecuriy) e ajuda na descoberta por buscas relacionadas a API Security.

```suggestion
#APISecurity #OWASP #BugBounty #CyberSecurity #BOLA #BFLA #PenetrationTesting #WebSecurity #EthicalHacking
```
</issue_to_address>

### Comment 10
<location path="social-media/posts/twitter/dia-16-scope.md" line_range="47" />
<code_context>
+```
+
+```
+Bonus: o log de auditoria em SQLite registra:
+• Cada ferramenta executada
+• O target testado
</code_context>
<issue_to_address>
**suggestion (typo):** Adicionar acento em "Bônus" para corrigir a ortografia em português.

Para manter a consistência com o restante do texto, sugiro usar "Bônus: o log de auditoria em SQLite registra:" em vez de "Bonus".

```suggestion
Bônus: o log de auditoria em SQLite registra:
```
</issue_to_address>

### Comment 11
<location path="social-media/threads_client.py" line_range="188" />
<code_context>
+            print("✏️  Abrindo formulário de novo post...")
+
+            # Tenta encontrar o botão de composição (vários seletores possíveis)
+            compose_selectors = [
+                'a[href="/compose"]',
+                '[aria-label="New thread"]',
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting shared helpers for selector lookup, text truncation, and container polling to remove repetition and make the Threads publishing flow easier to follow.

You can reduce quite a bit of cognitive load here with small helpers, without changing behavior.

### 1. Deduplicate selector probing logic

The loops for `compose_selectors`, `text_area_selectors` e `publish_selectors` are structurally identical. A small helper keeps all retry behavior but removes repetition:

```python
# helper (near other browser helpers)
async def first_visible(page, selectors, timeout_per_selector=3000):
    from playwright.async_api import TimeoutError as PWTimeout

    for selector in selectors:
        try:
            el = await page.wait_for_selector(
                selector, timeout=timeout_per_selector, state="visible"
            )
            if el:
                return el, selector
        except PWTimeout:
            continue
    return None, None
```

Then inside `post_via_browser`:

```python
# compose button
compose_btn, used = await first_visible(page, compose_selectors)
if compose_btn:
    print(f"   Encontrado: {used}")
    await compose_btn.click()
    await page.wait_for_timeout(1500)
else:
    print("   Tentando via URL /compose...")
    await page.goto(f"{THREADS_URL}/compose", wait_until="networkidle")
    await page.wait_for_timeout(2000)

# text area
text_area, used = await first_visible(page, text_area_selectors, timeout_per_selector=5000)
if not text_area:
    screenshot_path = Path(__file__).parent / "debug-screenshot.png"
    await page.screenshot(path=str(screenshot_path))
    raise RuntimeError(
        f"❌ Campo de texto não encontrado.\n"
        f"   Screenshot salvo em: {screenshot_path}\n"
        f"   A interface do Threads pode ter mudado."
    )
print(f"   Campo encontrado: {used}")

# publish button
publish_btn, used = await first_visible(page, publish_selectors)
if not publish_btn or not await publish_btn.is_enabled():
    screenshot_path = Path(__file__).parent / "debug-screenshot-publish.png"
    await page.screenshot(path=str(screenshot_path))
    raise RuntimeError(
        f"❌ Botão 'Publicar' não encontrado ou desabilitado.\n"
        f"   Screenshot salvo em: {screenshot_path}"
    )
print(f"   Botão encontrado: {used}")
await publish_btn.click()
```

This keeps the same semantics but flattens three similar code blocks into a single, tested pattern.

---

### 2. Centralize truncation logic

The max-length truncation is duplicated between API and browser modes. A tiny helper makes it consistent and easier to change later:

```python
def truncate_for_threads(text: str) -> tuple[str, bool]:
    if len(text) <= MAX_CHARS:
        return text, False
    return text[: MAX_CHARS - 3] + "...", True
```

Usage in `ThreadsAPIClient.post`:

```python
async def post(self, text: str) -> dict:
    text, was_truncated = truncate_for_threads(text)
    if was_truncated:
        print(f"⚠️  Texto tem {len(text)} chars (limite: {MAX_CHARS}). Truncando...")

    async with httpx.AsyncClient(timeout=30) as client:
        ...
```

And in `post_via_browser`:

```python
async def post_via_browser(...):
    text, was_truncated = truncate_for_threads(text)
    if was_truncated:
        print(f"⚠️  Texto tem {len(text)} chars (limite: {MAX_CHARS}). Truncando...")
    ...
```

This removes duplication and guarantees both modes stay in sync on any future change to truncation rules.

---

### 3. Extract container polling from API client

The polling loop inside `ThreadsAPIClient.post` mixes control flow, HTTP calls, and error handling. Extracting it makes `post()` read as a simple 3-step flow:

```python
async def _wait_for_container_ready(self, client: httpx.AsyncClient, container_id: str) -> None:
    print("⏳ Aguardando processamento...")
    for attempt in range(10):
        await asyncio.sleep(3)
        status_resp = await client.get(
            f"{THREADS_API_BASE}/{container_id}",
            params={
                "fields": "status,error_message",
                "access_token": self.access_token,
            },
        )
        status_resp.raise_for_status()
        data = status_resp.json()
        status = data.get("status", "")
        if status == "FINISHED":
            return
        if status == "ERROR":
            error_msg = data.get("error_message", "Erro desconhecido")
            raise RuntimeError(f"Container com erro: {error_msg}")
        print(f"   Status: {status} (tentativa {attempt + 1}/10)")
    raise TimeoutError("Container não ficou pronto em 30 segundos.")
```

Then `post()` becomes:

```python
async def post(self, text: str) -> dict:
    text, was_truncated = truncate_for_threads(text)
    if was_truncated:
        print(f"⚠️  Texto tem {len(text)} chars (limite: {MAX_CHARS}). Truncando...")

    async with httpx.AsyncClient(timeout=30) as client:
        print("📤 Criando container no Threads...")
        resp = await client.post(
            f"{THREADS_API_BASE}/{self.user_id}/threads",
            params={
                "media_type": "TEXT",
                "text": text,
                "access_token": self.access_token,
            },
        )
        resp.raise_for_status()
        container_id = resp.json()["id"]
        print(f"✅ Container criado: {container_id}")

        await self._wait_for_container_ready(client, container_id)

        print("🚀 Publicando...")
        pub_resp = await client.post(
            f"{THREADS_API_BASE}/{self.user_id}/threads_publish",
            params={"creation_id": container_id, "access_token": self.access_token},
        )
        pub_resp.raise_for_status()
        post_id = pub_resp.json()["id"]
        return {
            "success": True,
            "post_id": post_id,
            "url": f"https://www.threads.net/post/{post_id}",
            "mode": "api",
        }
```

This preserves all behavior and logs, but the main method is much easier to scan and reason about.
</issue_to_address>

### Comment 12
<location path="social-media/poster.py" line_range="320" />
<code_context>
    result = subprocess.run(cmd, capture_output=True, text=True)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread social-media/poster.py Outdated
Comment thread social-media/poster.py Outdated
Comment thread social-media/threads_client.py Outdated
Comment thread social-media/schedule.sh Outdated
Comment thread social-media/setup-windows.ps1 Outdated
Comment thread social-media/posts/twitter/dia-02-problema.md Outdated
Comment thread social-media/posts/linkedin/dia-12-api.md Outdated
Comment thread social-media/posts/twitter/dia-16-scope.md Outdated
Comment thread social-media/threads_client.py
Comment thread social-media/poster.py Outdated
claude added 2 commits May 24, 2026 21:56
Architectural:
- Centralize 30-day post mapping in posts-map.json (single source of truth)
  poster.py, schedule.sh and SKILL.md all load from this file — no more drift
- schedule.sh: remove GNU date -d fallback (not portable on macOS BSD date);
  require python3 and hard-fail with a clear message if absent

poster.py:
- extract_post_text: fix regex to accept language-tagged fences (e.g. ```txt)
  and CRLF line-endings: r'```[^\r\n]*\r?\n(.*?)\r?\n```'
- schedule_unix_uninstall: match only '# KamboSocialPoster' marker and skip
  the next line (the cron entry); no longer touches unrelated poster.py jobs
- schedule_windows_install: add comment noting /TR is built from sys.executable
  and Path(__file__) — trusted system paths, not user-controlled input

threads_client.py:
- Extract truncate_for_threads(text) -> (str, bool): single truncation path
  shared by API and browser modes
- Extract first_visible(page, selectors, timeout_ms) helper: eliminates three
  identical selector-probing loops (compose / textarea / publish button)
- Extract ThreadsAPIClient._wait_for_container_ready(): separates polling from
  the main post() flow
- Debug screenshots: use _debug_screenshot_path(label) which appends a full
  timestamp (%Y%m%d-%H%M%S-%f) so failure snapshots are never overwritten

setup-windows.ps1:
- Patch CHROME_PROFILE_DIR in .env line-by-line (not regex on whole file);
  append entry if key is absent; use Set-Content default (preserves newline)

Typo fixes in posts:
- dia-18-selfimprove.md: 'histórico objetividade' -> 'histórico objetivo'
- dia-11-evidence.md: 'model' -> 'modelo'
- dia-02-problema.md: 'gradu' -> 'gradua'
- dia-12-api.md: '#APISecuriy' -> '#APISecurity'
- dia-16-scope.md: 'Bonus:' -> 'Bônus:'

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA
Addresses opengrep python.lang.security.audit.dangerous-subprocess-use-audit.

schedule_windows_install: replace hand-built f-string /TR value with
subprocess.list2cmdline([python, script, 'today']) which applies correct
Windows cmd quoting for paths containing spaces or special characters.

schedule_unix_install: wrap each path component (BASE_DIR, python, script,
LOG_FILE) with shlex.quote() so the cron entry is safe for POSIX shells
regardless of install directory names.

Also adds 'import shlex' to the module imports.

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

Comment thread social-media/poster.py Outdated
claude added 2 commits May 24, 2026 22:08
…install

Both `python` (sys.executable) and `script` (Path(__file__).resolve()) are
resolved from the Python runtime itself — not from user-controlled input.
list2cmdline is used to correctly quote paths with spaces for the Windows
Task Scheduler /TR argument. Adding nosec B603/B607 annotations to silence
the static analyzer false positives.

https://claude.ai/code/session_01NLNppLCEKST4ZgS7ZbmLNA
@ktfth
ktfth merged commit 49ad47e into main May 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants