Skip to content

Commit 1ff6d86

Browse files
script for socials to buffer (#1589)
- add a script to put the posts for sponsors and speakers to buffer directly (no need to use zapier) - fix bugs in the script for generating the combined queue - add documentation - add the json with queue for 2026 for visibility - add missing partner cards --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 04e3ef0 commit 1ff6d86

43 files changed

Lines changed: 3018 additions & 18 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,6 @@ storybook-static/
142142

143143
# EuroPython website
144144
src/content/days
145+
146+
# Local secrets (never commit)
147+
.env.local

docs/social-media-scheduling.md

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# Social Media Scheduling
2+
3+
This document describes the end-to-end process for generating social media cards
4+
and scheduling posts for EuroPython 2026 speakers, sponsors, and community
5+
partners via Buffer.
6+
7+
---
8+
9+
## Overview
10+
11+
The pipeline has three stages:
12+
13+
1. **Generate social card images** — render PNG cards for speakers and sponsors
14+
using Puppeteer
15+
2. **Generate the post queue** — hit the Astro API endpoint to produce
16+
`queue.json`
17+
3. **Schedule posts to Buffer** — run the Python script to push items from the
18+
queue
19+
20+
---
21+
22+
## Prerequisites
23+
24+
- Dev server running locally (`pnpm dev`, default port `4321`)
25+
- Node.js + pnpm installed
26+
- Python environment with dependencies: `requests`, `python-dotenv`
27+
- A `.env.local` file in the project root with your Buffer API key:
28+
29+
```
30+
BUFFER_API_KEY=your_buffer_api_key_here
31+
```
32+
33+
This file is gitignored and never committed.
34+
35+
### Getting the Buffer API key
36+
37+
1. Log in to [buffer.com](https://buffer.com) with the EuroPython account
38+
2. Go to **Account Settings****Apps & Integrations** (or navigate directly to
39+
https://account.buffer.com/apps)
40+
3. Under **Access Token**, copy your personal access token
41+
4. Paste it as the value of `BUFFER_API_KEY` in your `.env.local`
42+
43+
---
44+
45+
## Step 1 — Generate Social Card Images
46+
47+
Social cards are 900×900px PNG images rendered from Astro pages and
48+
screenshotted with Puppeteer.
49+
50+
### Speaker cards
51+
52+
```bash
53+
node scripts/download_social_speakers.cjs
54+
```
55+
56+
Screenshots are saved to the current directory. Move them to the right place:
57+
58+
```bash
59+
mv social-*.png public/media/speakers/
60+
```
61+
62+
### Sponsor & partner cards
63+
64+
```bash
65+
node scripts/download_social_sponsors.cjs
66+
```
67+
68+
Move them:
69+
70+
```bash
71+
mv social-*.png public/media/sponsors/
72+
```
73+
74+
> The scripts read from `http://localhost:4321/media/speakers` and
75+
> `http://localhost:4321/media/sponsors` respectively, so the dev server must be
76+
> running.
77+
78+
### Checking for missing images
79+
80+
Cross-reference the live site with what's in `public/media/sponsors/`. Every
81+
sponsor and partner listed on:
82+
83+
- https://ep2026.europython.eu/sponsors/
84+
- https://ep2026.europython.eu/community-partners/
85+
86+
should have a corresponding `social-<slug>.png` in `public/media/sponsors/`.
87+
88+
---
89+
90+
## Step 2 — Generate the Post Queue
91+
92+
The queue is a JSON file that interleaves speakers, sponsors, and community
93+
partners in a repeating pattern:
94+
95+
```
96+
speaker → speaker → sponsor → speaker → partner → (repeat)
97+
```
98+
99+
Regenerate it by hitting the API endpoint while the dev server is running:
100+
101+
```bash
102+
curl -s http://localhost:4321/api/media/combined_socials_queue > src/pages/api/media/combined_socials_queue.json
103+
```
104+
105+
This overwrites `src/pages/api/media/combined_socials_queue.json` with all 150+
106+
items sorted and interleaved.
107+
108+
### Tier classification
109+
110+
Sponsors are split into two buckets:
111+
112+
- **Commercial** (go into the sponsor slot): Keystone, Diamond, Platinum,
113+
Platinum X, Gold, Silver, Bronze, Patron
114+
- **Community partners** (go into the partner slot): Partners, Supporters,
115+
Financial Aid
116+
117+
If a new sponsor tier is added, update `commercialTiers` in
118+
`src/pages/api/media/combined_socials_queue.ts`.
119+
120+
### Manual queue adjustments
121+
122+
You can edit `src/pages/api/media/combined_socials_queue.json` directly to
123+
reorder entries. For example, to swap two sponsors:
124+
125+
```python
126+
python3 -c "
127+
import json
128+
path = 'src/pages/api/media/combined_socials_queue.json'
129+
with open(path) as f:
130+
q = json.load(f)
131+
q[2], q[17] = q[17], q[2] # swap positions 3 and 18 (0-indexed)
132+
with open(path, 'w') as f:
133+
json.dump(q, f, indent=2, ensure_ascii=False)
134+
"
135+
```
136+
137+
`src/pages/api/media/combined_socials_queue.json` is committed to the repo. Any
138+
manual reordering should be committed so the intentional order is preserved and
139+
not lost when the queue is regenerated.
140+
141+
---
142+
143+
## Step 3 — Commit and Merge Images
144+
145+
Before scheduling, the social card images need to be live on the production
146+
site. Buffer fetches the image URL at scheduling time and will fail with
147+
`Failed to fetch image dimensions: Not Found` if the file isn't deployed yet.
148+
149+
1. Stage the new images:
150+
151+
```bash
152+
git add public/media/speakers/ public/media/sponsors/
153+
```
154+
155+
2. Commit:
156+
157+
```bash
158+
git commit -m "Add social media cards for speakers/sponsors"
159+
```
160+
161+
3. Push to a branch and open a PR:
162+
163+
```bash
164+
git push -u origin your-branch-name
165+
gh pr create --title "Add social media cards" --body "New speaker and sponsor social card PNGs for Buffer scheduling."
166+
```
167+
168+
4. Wait for the PR to be merged and deployed before proceeding to Step 4.
169+
170+
You can verify the images are live by checking a URL like:
171+
`https://ep2026.europython.eu/media/sponsors/social-arm.png`
172+
173+
---
174+
175+
## Step 4 — Schedule Posts via Buffer
176+
177+
> ⚠️ **Images must be live before scheduling.** Buffer fetches the image URL at
178+
> scheduling time. If the PNG hasn't been deployed yet (i.e. the PR adding it
179+
> hasn't been merged and deployed), Buffer will fail with
180+
> `Failed to fetch image dimensions: Not Found`. Always merge and confirm the
181+
> images are live at `https://ep2026.europython.eu/media/speakers/` or
182+
> `https://ep2026.europython.eu/media/sponsors/` before running the script.
183+
184+
### How Buffer scheduling works
185+
186+
Buffer operates as a FIFO queue against pre-configured time slots:
187+
188+
- Time slots are defined per channel inside Buffer (e.g. "Twitter, weekdays at
189+
09:00 and 14:00").
190+
- Incoming posts fill the next available slot in order — you don't pick a
191+
specific date/time.
192+
- Slots can be added or shifted directly in Buffer if you need to change the
193+
cadence.
194+
195+
> ⚠️ **Check your Buffer slots before running the script.** If you push 120
196+
> cards and there is only one slot per day per channel, you'll end up with posts
197+
> queued months out — and there is no bulk deletion in Buffer, so you'd have to
198+
> remove them one by one. Before each run, open Buffer and verify the number and
199+
> cadence of slots for each channel matches your intended rollout pace.
200+
201+
The scheduling script is at `src/pages/api/media/buffer-scheduling.py`.
202+
203+
### Configuration
204+
205+
Open the script and set the range of queue items to schedule:
206+
207+
```python
208+
QUEUE_START = 1 # first item (1-based, inclusive)
209+
QUEUE_END = 5 # last item (1-based, inclusive)
210+
```
211+
212+
It's a good idea to start with a small batch (e.g. 1–5) to verify everything
213+
looks correct in Buffer before pushing the full queue. Once you're happy with
214+
the results, increment the range for subsequent runs.
215+
216+
### Running the script
217+
218+
```bash
219+
uv run python src/pages/api/media/buffer-scheduling.py
220+
```
221+
222+
Or with your venv activated:
223+
224+
```bash
225+
python src/pages/api/media/buffer-scheduling.py
226+
```
227+
228+
### What it does
229+
230+
1. Connects to Buffer and fetches your channel profile IDs
231+
2. Iterates over the selected queue items
232+
3. For each item, posts to every channel that has text defined (`instagram`,
233+
`x`, `linkedin`, `bsky`, `fosstodon`)
234+
4. Skips channels with empty text or no matching Buffer profile
235+
5. Adds Instagram-specific metadata (`postType: post`) automatically
236+
6. Waits 1.5 seconds between items to respect rate limits
237+
238+
### Channel notes
239+
240+
- **Instagram**: requires `postType: post` — handled automatically
241+
- **Sponsors/partners**: no Instagram channel (only x, linkedin, bsky,
242+
fosstodon)
243+
- **Speakers**: all 5 channels including Instagram
244+
- **Bluesky**: Buffer returns this as `"bluesky"` — normalized to `"bsky"`
245+
automatically
246+
247+
### Tracking progress
248+
249+
After each successful run, note the last `QUEUE_END` value. The next run should
250+
set `QUEUE_START = previous QUEUE_END + 1`.
251+
252+
Already scheduled as of initial setup:
253+
254+
- Positions 1–3: Abhik Sarkar, Abhimanyu Singh Shekhawat, Abigail Afi Gbadago
255+
256+
Next batch (positions 4–8): Adam Gorgoń, Alejandro Cabello Jiménez,
257+
ActiveCampaign, Aleksander, 1Password
258+
259+
---
260+
261+
## Troubleshooting
262+
263+
| Error | Cause | Fix |
264+
| --------------------------------------------- | ------------------------------- | ---------------------------------------------------------- |
265+
| `BUFFER_API_KEY not set` | Missing `.env.local` | Create `.env.local` with the key |
266+
| `Failed to fetch image dimensions: Not Found` | Image not deployed yet | Generate and commit the missing PNG first |
267+
| `Field "postType" is not defined` | Wrong field placement | `postType` goes inside `metadata.instagram`, not top-level |
268+
| `Channel profile not connected` | Service name mismatch | Check the normalization block in the script |
269+
| `KeyError: 'channel'` | Queue item missing channel data | Re-run `curl .../queue > queue.json` to regenerate |
177 KB
Loading
175 KB
Loading
175 KB
Loading
-3.57 KB
Loading
-3.71 KB
Loading
-3.83 KB
Loading
-3.86 KB
Loading
-5.99 KB
Loading

0 commit comments

Comments
 (0)