social-autoposter 1.3.7 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-autoposter",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Install as a Claude Code agent skill.",
5
5
  "bin": {
6
6
  "social-autoposter": "bin/cli.js"
@@ -449,13 +449,27 @@ def _ban_entries_to_subs(entries) -> set[str]:
449
449
 
450
450
 
451
451
  def _make_ban_entry(sub: str, reason: str | None, project: str | None) -> dict:
452
- """Build a new ban-list entry with the current UTC timestamp."""
452
+ """Build a new ban-list entry with the current UTC timestamp.
453
+
454
+ Stamps the current Reddit account (top-level config.json reddit_account
455
+ .username) so per-account scoping in reddit_tools._load_comment_blocked_subs
456
+ can ignore this entry on other machines posting as a different account.
457
+ Returns account=None if the config has no reddit_account, in which case
458
+ the reader treats the entry as global (back-compat with pre-2026-05-15).
459
+ """
453
460
  from datetime import datetime, timezone
461
+ account = None
462
+ try:
463
+ with open(CONFIG_PATH) as _f:
464
+ account = (json.load(_f).get("reddit_account") or {}).get("username") or None
465
+ except Exception:
466
+ pass
454
467
  return {
455
468
  "sub": sub.strip().lower(),
456
469
  "added_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
457
470
  "reason": reason or None,
458
471
  "project": project or None,
472
+ "account": account,
459
473
  }
460
474
 
461
475
 
@@ -190,6 +190,13 @@ def _load_comment_blocked_subs(project_name=None):
190
190
  config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "config.json")
191
191
  with open(config_path) as f:
192
192
  config = json.load(f)
193
+ # Per-account scoping (2026-05-15): a ban applies only to the account
194
+ # that triggered it. Different machines may post the same project as
195
+ # different accounts (laptop=Deep_Ad1959, sandbox VM=StreetRefuse7512);
196
+ # without this filter, account A's real ban would suppress a sub for
197
+ # account B that has no such ban. Entries with account=null are
198
+ # treated as global (apply regardless), preserving pre-2026-05-15 data.
199
+ current_account = (config.get("reddit_account") or {}).get("username") or None
193
200
  blocked = set()
194
201
  bans = config.get("subreddit_bans") or {}
195
202
  if isinstance(bans, dict):
@@ -198,8 +205,15 @@ def _load_comment_blocked_subs(project_name=None):
198
205
  if not slug:
199
206
  continue
200
207
  entry_project = None
208
+ entry_account = None
201
209
  if isinstance(entry, dict):
202
210
  entry_project = entry.get("project") or None
211
+ entry_account = entry.get("account") or None
212
+ # Account filter first: if entry is tagged with a specific
213
+ # account and it's not the current one, this ban doesn't apply.
214
+ if (entry_account is not None and current_account is not None
215
+ and entry_account.lower() != current_account.lower()):
216
+ continue
203
217
  if entry_project is None:
204
218
  blocked.add(slug)
205
219
  elif project_name and entry_project.lower() == project_name.lower():
@@ -214,7 +214,16 @@ def upsert_candidates(tweets, config, batch_id=None):
214
214
  bookmarks_t1 = NULL,
215
215
  t1_checked_at = NULL,
216
216
  delta_score = NULL,
217
- batch_id = COALESCE(EXCLUDED.batch_id, twitter_candidates.batch_id)
217
+ batch_id = COALESCE(twitter_candidates.batch_id, EXCLUDED.batch_id)
218
+ WHERE NOT (
219
+ twitter_candidates.status = 'pending'
220
+ AND twitter_candidates.batch_id IS DISTINCT FROM EXCLUDED.batch_id
221
+ AND EXISTS (
222
+ SELECT 1 FROM twitter_batches tb
223
+ WHERE tb.batch_id = twitter_candidates.batch_id
224
+ AND tb.phase_started_at > NOW() - INTERVAL '20 minutes'
225
+ )
226
+ )
218
227
  """,
219
228
  [
220
229
  url,
@@ -117,7 +117,7 @@ def update_candidate(cid: int, status: str) -> None:
117
117
  return
118
118
  cmd = [
119
119
  "psql", DATABASE_URL, "-c",
120
- f"UPDATE twitter_candidates SET status='{sql_status}' WHERE id={cid}",
120
+ f"UPDATE twitter_candidates SET status='{sql_status}' WHERE id={cid} AND status != 'posted'",
121
121
  ]
122
122
  rc, out, err = run_subprocess(cmd, timeout_sec=30)
123
123
  if rc != 0: