Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/social-draft.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Issue #27 — social presence with community co-created posts.
# On every release (or on demand), draft a social post from that version's
# CHANGELOG section and open a PR with it under social/drafts/. ALWAYS a
# draft + PR — a human reviews, edits, and publishes; nothing goes out alone.
#
# The ANTHROPIC_API_KEY secret is OPTIONAL: with it the draft is AI-written
# (grounded only in the changelog); without it a template draft is assembled
# from the changelog bullets. Either way the flow works.
name: Social draft

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version to draft (e.g. 1.4.0); empty = latest released'
required: false

permissions:
contents: write
pull-requests: write

jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Draft the post from the CHANGELOG
run: node scripts/social-draft.mjs "${{ inputs.version || github.event.release.tag_name }}"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Open a PR with the draft
run: |
if git diff --quiet && [ -z "$(git status --porcelain social/)" ]; then
echo "no draft produced — nothing to PR"; exit 0
fi
VERSION="${{ inputs.version || github.event.release.tag_name }}"
BRANCH="social/draft-${VERSION:-latest}-${{ github.run_id }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add social/
git commit -m "social: rascunho de post da versão ${VERSION:-mais recente}"
git push origin "$BRANCH"
gh pr create \
--title "Social: rascunho de post — ${VERSION:-versão mais recente}" \
--body "Rascunho gerado automaticamente a partir do CHANGELOG (issue #27).

**Antes de publicar:** revise o texto, ajuste o tom, confira que nada foi inventado e dê os créditos de quem contribuiu na versão (CONTRIBUTORS.md). O guia completo está em \`social/README.md\`." \
--label "social/content" || true
env:
GH_TOKEN: ${{ github.token }}
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ Todas as mudanças relevantes deste projeto são documentadas neste arquivo.
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/)
e o projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).

## [Não lançado]

### Adicionado (ferramentas do repositório)

- **Rascunho de post por release** (`.github/workflows/social-draft.yml` +
`scripts/social-draft.mjs`, issue #27): a cada release, a seção do CHANGELOG
vira um rascunho de post e um PR em `social/drafts/` para revisão humana —
nada publica sozinho. Com o segredo `ANTHROPIC_API_KEY` o texto é escrito
por IA (limitado ao que o changelog diz); sem ele, sai um template dos
bullets. Guia de co-criação da comunidade em `social/README.md`.

## [1.4.0] - 2026-07-07

### Adicionado
Expand Down
170 changes: 170 additions & 0 deletions scripts/social-draft.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// ZeroDelay — keep YouTube live streams in real time
// Author: João Gustavo França <joao@solitus.com.br> (https://github.com/joaogfc)
//
// Drafts a social post from a released CHANGELOG section (issue #27): every
// release turns into a REVIEWABLE draft under social/drafts/ — never a direct
// publication. The workflow (.github/workflows/social-draft.yml) opens a PR
// with the draft; a human edits, approves, and posts it.
//
// node scripts/social-draft.mjs # latest released version
// node scripts/social-draft.mjs 1.4.0 # a specific version
//
// With ANTHROPIC_API_KEY set the draft is written by Claude, grounded ONLY in
// the changelog section (repo tooling — the extension itself never talks to
// any server). Without the key it falls back to a template draft assembled
// from the changelog bullets — the workflow works with zero secrets.
//
// Raw fetch on purpose: every script in this repo is dependency-free (see
// scripts/build.mjs), and one small completions call doesn't justify an SDK.
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

const root = fileURLToPath(new URL('..', import.meta.url));
const MODEL = process.env.SOCIAL_MODEL || 'claude-opus-4-8';
const STORE_CHROME = 'https://chromewebstore.google.com/detail/zerodelay/gblbnnkemjblakamnbclcehoaobnhlpm';
const STORE_FIREFOX = 'https://addons.mozilla.org/firefox/addon/zerodelay/';
const REPO = 'https://github.com/joaogfc/ZeroDelay';

// --- CHANGELOG parsing ------------------------------------------------------
// Sections look like "## [1.4.0] - 2026-07-07"; "## [Não lançado]" is skipped
// when resolving the default (you announce releases, not work in flight).
function parseChangelog(text) {
const sections = [];
const re = /^## \[([^\]]+)\](?: - (\S+))?$/gm;
let match, prev = null;
while ((match = re.exec(text)) !== null) {
if (prev) prev.body = text.slice(prev.end, match.index).trim();
prev = { version: match[1], date: match[2] || null, end: re.lastIndex };
sections.push(prev);
}
if (prev) prev.body = text.slice(prev.end).trim();
return sections;
}

// --- Template fallback (no API key) ----------------------------------------
// First-level bullets (with their wrapped continuation lines joined), markdown
// links/emphasis stripped, each trimmed to headline length.
function templateDraft(version, body) {
const raw = [];
for (const line of body.split('\n')) {
const start = line.match(/^- (.+)$/);
const cont = line.match(/^ {2,}(\S.*)$/);
if (start) raw.push(start[1]);
else if (cont && raw.length) raw[raw.length - 1] += ' ' + cont[1];
}
const bullets = raw.slice(0, 4).map(text => {
const clean = text
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') // [text](url) -> text
.replace(/\*\*([^*]+)\*\*/g, '$1') // **bold** -> bold
.replace(/`([^`]+)`/g, '$1');
// Headline = up to the first sentence end (or hard cap), never mid-word.
const firstStop = clean.search(/[.:!?] /);
const head = firstStop > 15 ? clean.slice(0, firstStop) : clean;
return '• ' + (head.length > 110 ? head.slice(0, 107).replace(/\s+\S*$/, '') + '…' : head);
});
return [
`🔴 ZeroDelay v${version} chegou!`,
'',
...bullets,
'',
`Chrome: ${STORE_CHROME}`,
`Firefox: ${STORE_FIREFOX}`,
].join('\n');
}

// --- Claude draft (optional) ------------------------------------------------
async function claudeDraft(version, body) {
const prompt = [
`Você escreve os posts do ZeroDelay, uma extensão open-source (GPL) que`,
`mantém lives do YouTube em tempo real. Abaixo está a seção do CHANGELOG`,
`da versão ${version}. Escreva DOIS rascunhos de post em pt-BR:`,
'',
'1. **X/Twitter** (máx ~280 caracteres): direto, uma melhoria em destaque,',
' tom de quem fala com espectador de live, zero jargão corporativo.',
'2. **Instagram** (caption curta): 2-4 linhas + até 4 hashtags discretas.',
'',
'Regras: baseie-se SOMENTE no changelog abaixo (não invente recursos);',
'nada de hype vazio ("revolucionário", "incrível"); pode usar 1-2 emojis;',
`termine com o link da loja: ${STORE_CHROME}`,
'',
'--- CHANGELOG ---',
body,
].join('\n');

const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: MODEL,
max_tokens: 2048,
messages: [{ role: 'user', content: prompt }],
}),
});
if (!res.ok) throw new Error(`Claude API: HTTP ${res.status} — ${(await res.text()).slice(0, 300)}`);
const data = await res.json();
if (data.stop_reason === 'refusal') throw new Error('Claude API: request refused');
const text = (data.content || []).filter(b => b.type === 'text').map(b => b.text).join('\n').trim();
if (!text) throw new Error('Claude API: empty response');
return text;
}

// --- Main --------------------------------------------------------------------
const changelog = readFileSync(join(root, 'CHANGELOG.md'), 'utf8');
const sections = parseChangelog(changelog);

const wanted = (process.argv[2] || '').replace(/^v/, '');
const section = wanted
? sections.find(s => s.version === wanted)
: sections.find(s => /^\d/.test(s.version)); // newest released (skips "Não lançado")

if (!section || !section.body) {
console.error(`✗ CHANGELOG section not found${wanted ? ` for version ${wanted}` : ''}.`);
process.exit(1);
}

let draft, source;
if (process.env.ANTHROPIC_API_KEY) {
try {
draft = await claudeDraft(section.version, section.body);
source = `IA (${MODEL})`;
} catch (e) {
console.error(`! ${e.message} — falling back to the template draft.`);
}
}
if (!draft) {
draft = templateDraft(section.version, section.body);
source = source || 'template (sem ANTHROPIC_API_KEY)';
}

const outDir = join(root, 'social', 'drafts');
mkdirSync(outDir, { recursive: true });
const outPath = join(outDir, `v${section.version}.md`);
if (existsSync(outPath)) console.error(`! overwriting existing draft: social/drafts/v${section.version}.md`);

writeFileSync(outPath, [
'---',
'status: rascunho # vira "aprovado" só depois de revisão humana',
`versao: ${section.version}`,
`fonte: ${source}`,
`gerado_em: ${new Date().toISOString()}`,
'---',
'',
`# Rascunho de post — ZeroDelay v${section.version}`,
'',
'> Revisão humana obrigatória antes de publicar (social/README.md).',
`> Créditos de quem contribuiu na versão: ver CONTRIBUTORS.md e o CHANGELOG.`,
'',
draft,
'',
'---',
'',
`Repositório: ${REPO}`,
'',
].join('\n'));

console.log(`✓ social/drafts/v${section.version}.md (${source})`);
36 changes: 36 additions & 0 deletions social/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Social — posts co-criados pela comunidade

Materialização da [issue #27](https://github.com/joaogfc/ZeroDelay/issues/27):
o ZeroDelay melhora quase todo dia, mas as novidades ficavam escondidas no
CHANGELOG. Esta pasta é o canal entre o repositório e as redes sociais.

## Como funciona

1. **A cada release**, o workflow [`social-draft.yml`](../.github/workflows/social-draft.yml)
lê a seção da versão no `CHANGELOG.md` e abre um PR com um rascunho de post
em [`drafts/`](drafts/) — escrito por IA quando o segredo `ANTHROPIC_API_KEY`
está configurado (baseado SOMENTE no changelog), ou montado dos bullets do
changelog quando não está.
2. **Um humano revisa** o rascunho no PR: ajusta o tom, corta exagero, confere
que nada foi inventado. Nada é publicado sem essa revisão — o workflow não
tem acesso a nenhuma rede social, de propósito.
3. **Publicação é manual** nos perfis oficiais, por quem tem acesso.

## Co-criação

Qualquer pessoa pode propor posts:

- Abra uma issue com o label **`social/content`** com a ideia ou o texto; ou
- Mande um PR direto com um arquivo em `drafts/` (use o frontmatter dos
rascunhos gerados como modelo).

Ideias aprovadas viram publicação **com crédito ao autor**, no espírito do
[`CONTRIBUTORS.md`](../CONTRIBUTORS.md).

## Regras editoriais

- Tom de quem assiste live: direto, leve, zero jargão corporativo.
- Nunca prometer o que a extensão não faz; o changelog é o limite do factual.
- O "antes e depois" (live atrasada → tempo real) é a história principal.
- Privacidade é argumento, não rodapé: tudo roda local, nada é coletado.
- Melhorias de comunidade citam o autor e o PR (como o CHANGELOG já faz).
25 changes: 25 additions & 0 deletions social/drafts/v1.4.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
status: rascunho # vira "aprovado" só depois de revisão humana
versao: 1.4.0
fonte: template (sem ANTHROPIC_API_KEY)
gerado_em: 2026-07-09T14:16:00.325Z
---

# Rascunho de post — ZeroDelay v1.4.0

> Revisão humana obrigatória antes de publicar (social/README.md).
> Créditos de quem contribuiu na versão: ver CONTRIBUTORS.md e o CHANGELOG.

🔴 ZeroDelay v1.4.0 chegou!

• Convite de doação dentro do player (Brasil)
• "Temperatura" dos modos (ideia e implementação de @leandroohsr, PR #41)
• Valores de doação ancorados um pouco mais para cima
• Freio anti-travamento mais conservador

Chrome: https://chromewebstore.google.com/detail/zerodelay/gblbnnkemjblakamnbclcehoaobnhlpm
Firefox: https://addons.mozilla.org/firefox/addon/zerodelay/

---

Repositório: https://github.com/joaogfc/ZeroDelay