Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/social.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: social

on:
schedule:
- cron: "0 15 * * 1-5"
# Three tries a day: GitHub runs cron late or not at all under load, and :00 is the busiest minute.
# post.mjs posts at most one tweet per day from the schedule.
- cron: "17 15,18,21 * * 1-5"
workflow_dispatch:
inputs:
mode:
Expand Down
2 changes: 1 addition & 1 deletion social/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Social posting

`queue.json` holds every planned tweet with its date. The `social` workflow runs at 15:00 UTC on weekdays and posts **one** tweet: the oldest due one that is ready. Nothing is ever posted twice; state lives in the "Social posting log" issue.
`queue.json` holds every planned tweet with its date. The `social` workflow runs on weekdays at 15:17 UTC and posts **one** tweet: the oldest due one that is ready. It runs again at 18:17 and 21:17 in case GitHub runs the schedule late or skips it; those runs post nothing if a tweet already went out that day. Nothing is ever posted twice; state lives in the "Social posting log" issue.

- **Text-only tweets** post on their date.
- **Tweets with a visual** wait for the file at `social/media/<id>.mp4` (or `.png`, `.jpg`, `.gif`, `.webp`, `.mov`). Images post as soon as they are there and the date has come.
Expand Down
9 changes: 7 additions & 2 deletions social/post.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ function logBody(rows, state) {
const done = state.posted[e.id];
return `| ${e.date} | #${e.id} | ${done ? `[posted](https://x.com/i/status/${done.tweet})` : status} | ${e.text.slice(0, 60).replace(/\|/g, '/')}… |`;
});
return `Updated by the \`social\` workflow at each run. One tweet posts per weekday at 15:00 UTC: the oldest due one that is ready. Media goes in \`social/media/<id>.mp4\` (or .png/.jpg/.gif); videos need the \`${LABEL.approved}\` label on their approval issue.
return `Updated by the \`social\` workflow at each run. One tweet posts per weekday from 15:17 UTC: the oldest due one that is ready. Media goes in \`social/media/<id>.mp4\` (or .png/.jpg/.gif); videos need the \`${LABEL.approved}\` label on their approval issue.

| Date | Tweet | Status | Text |
| --- | --- | --- | --- |
Expand Down Expand Up @@ -191,7 +191,12 @@ async function run(write) {
rows.push({ e, file, approval, status });
}

const next = rows.find((r) => r.status === 'ready');
// The schedule fires several times a day because GitHub delays or drops cron runs; only the first one that finds a tweet posts.
// A manual `post` run always posts.
const postedToday = Object.values(state.posted).some((p) => p.at.slice(0, 10) === today);
const scheduled = process.env.GITHUB_EVENT_NAME === 'schedule';
const next = scheduled && postedToday ? undefined : rows.find((r) => r.status === 'ready');
if (scheduled && postedToday) console.log('already posted today');
console.log(rows.filter((r) => r.status !== 'posted' && daysUntil(r.e.date) <= LEAD_DAYS).map((r) => `#${r.e.id} ${r.e.date} ${r.status}`).join('\n') || 'nothing due');
console.log(next ? `next: #${next.e.id} "${next.e.text.slice(0, 70)}…"${next.file ? ` + ${next.file.rel}` : ''}` : 'nothing to post');

Expand Down
Loading