social-autoposter 1.3.8 → 1.3.9

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.8",
3
+ "version": "1.3.9",
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"
@@ -295,22 +295,52 @@ def _refresh_browser_lock():
295
295
 
296
296
 
297
297
  def get_browser_and_page(playwright):
298
- """Connect to the reddit-agent MCP browser via CDP with a fresh logged-in context.
299
-
300
- Creates a NEW browser context with storageState cookies (the logged-in session)
301
- rather than reusing contexts[0] (the default context, which is NOT logged in).
302
- The MCP's isolated context is invisible to CDP connections, so we must create
303
- our own context with the same storageState.
304
-
305
- Returns (browser, page, is_cdp). When is_cdp=True, `page` is in a new context
306
- on the CDP browser. When is_cdp=False, it's a new headless page.
298
+ """Get a logged-in Reddit page, preferring CDP-attach over launch_persistent_context.
299
+
300
+ Two paths:
301
+ 1. CDP-attach (preferred on appmaker/e2b VM and any host running a visible
302
+ logged-in Chromium): connect to the existing browser, find a context with
303
+ a live reddit_session cookie, open a NEW PAGE on that context.
304
+ 2. launch_persistent_context fallback: when CDP isn't available OR contexts
305
+ have no reddit_session (laptop where reddit-agent MCP isolates its session
306
+ in an invisible context).
307
+
308
+ Why CDP-attach matters: appmaker's visible Chromium permanently holds
309
+ /root/.chromium-profile. launch_persistent_context collides on profile leveldb
310
+ locks, loads a partial session, and EVERY post returns account_blocked_in_sub
311
+ because the comment form never renders. Attaching to the live context dodges
312
+ the collision entirely.
313
+
314
+ Returns (browser, page, is_cdp). When is_cdp=True, callers must close ONLY
315
+ the page (not page.context) and NOT the browser; closing context[0] or the
316
+ CDP browser would kill the user's visible session.
307
317
  """
308
318
  _acquire_browser_lock()
309
319
  cdp_port = find_reddit_cdp_port()
310
320
 
311
- # Always use the persistent profile directly. CDP connections to the MCP
312
- # browser expose a default context that is NOT logged in (the MCP's logged-in
313
- # context is isolated/invisible to CDP), causing auth failures.
321
+ if cdp_port:
322
+ try:
323
+ cdp_browser = playwright.chromium.connect_over_cdp(f"http://localhost:{cdp_port}")
324
+ for ctx in cdp_browser.contexts:
325
+ try:
326
+ cookies = ctx.cookies("https://www.reddit.com/")
327
+ except Exception:
328
+ cookies = []
329
+ has_session = any(
330
+ c.get("name") == "reddit_session" and c.get("value")
331
+ for c in cookies
332
+ )
333
+ if has_session:
334
+ page = ctx.new_page()
335
+ return cdp_browser, page, True
336
+ try:
337
+ cdp_browser.close()
338
+ except Exception:
339
+ pass
340
+ except Exception:
341
+ pass
342
+
343
+ # Fallback: launch our own persistent context against PROFILE_DIR.
314
344
  # Retry on Chromium SingletonLock collisions (MCP holds the OS-level profile
315
345
  # lock for its entire server lifetime; the JSON lock can expire while the
316
346
  # OS lock is still held).
@@ -496,9 +526,18 @@ def post_comment(thread_url, text):
496
526
  }
497
527
 
498
528
  finally:
499
- page.context.close()
529
+ try:
530
+ if is_cdp:
531
+ page.close()
532
+ else:
533
+ page.context.close()
534
+ except Exception:
535
+ pass
500
536
  if not is_cdp:
501
- browser.close()
537
+ try:
538
+ browser.close()
539
+ except Exception:
540
+ pass
502
541
 
503
542
 
504
543
  def reply_to_comment(comment_permalink, text, dm_id=None):
@@ -736,9 +775,18 @@ def reply_to_comment(comment_permalink, text, dm_id=None):
736
775
  }
737
776
 
738
777
  finally:
739
- page.context.close()
778
+ try:
779
+ if is_cdp:
780
+ page.close()
781
+ else:
782
+ page.context.close()
783
+ except Exception:
784
+ pass
740
785
  if not is_cdp:
741
- browser.close()
786
+ try:
787
+ browser.close()
788
+ except Exception:
789
+ pass
742
790
 
743
791
 
744
792
  def edit_comment(comment_permalink, new_text):
@@ -859,9 +907,18 @@ def edit_comment(comment_permalink, new_text):
859
907
  }
860
908
 
861
909
  finally:
862
- page.context.close()
910
+ try:
911
+ if is_cdp:
912
+ page.close()
913
+ else:
914
+ page.context.close()
915
+ except Exception:
916
+ pass
863
917
  if not is_cdp:
864
- browser.close()
918
+ try:
919
+ browser.close()
920
+ except Exception:
921
+ pass
865
922
 
866
923
 
867
924
  def edit_thread(thread_permalink, new_body):
@@ -956,9 +1013,18 @@ def edit_thread(thread_permalink, new_body):
956
1013
  }
957
1014
 
958
1015
  finally:
959
- page.context.close()
1016
+ try:
1017
+ if is_cdp:
1018
+ page.close()
1019
+ else:
1020
+ page.context.close()
1021
+ except Exception:
1022
+ pass
960
1023
  if not is_cdp:
961
- browser.close()
1024
+ try:
1025
+ browser.close()
1026
+ except Exception:
1027
+ pass
962
1028
 
963
1029
 
964
1030
  def unread_dms():
@@ -1135,9 +1201,18 @@ def unread_dms():
1135
1201
  return unique
1136
1202
 
1137
1203
  finally:
1138
- page.context.close()
1204
+ try:
1205
+ if is_cdp:
1206
+ page.close()
1207
+ else:
1208
+ page.context.close()
1209
+ except Exception:
1210
+ pass
1139
1211
  if not is_cdp:
1140
- browser.close()
1212
+ try:
1213
+ browser.close()
1214
+ except Exception:
1215
+ pass
1141
1216
 
1142
1217
 
1143
1218
  def read_conversation(chat_url, max_messages=20):
@@ -1293,9 +1368,18 @@ def read_conversation(chat_url, max_messages=20):
1293
1368
  return result
1294
1369
 
1295
1370
  finally:
1296
- page.context.close()
1371
+ try:
1372
+ if is_cdp:
1373
+ page.close()
1374
+ else:
1375
+ page.context.close()
1376
+ except Exception:
1377
+ pass
1297
1378
  if not is_cdp:
1298
- browser.close()
1379
+ try:
1380
+ browser.close()
1381
+ except Exception:
1382
+ pass
1299
1383
 
1300
1384
 
1301
1385
  def _load_active_reddit_campaigns_for_dm():
@@ -1564,9 +1648,18 @@ def send_dm(chat_url, message, dm_id=None):
1564
1648
  }
1565
1649
 
1566
1650
  finally:
1567
- page.context.close()
1651
+ try:
1652
+ if is_cdp:
1653
+ page.close()
1654
+ else:
1655
+ page.context.close()
1656
+ except Exception:
1657
+ pass
1568
1658
  if not is_cdp:
1569
- browser.close()
1659
+ try:
1660
+ browser.close()
1661
+ except Exception:
1662
+ pass
1570
1663
 
1571
1664
 
1572
1665
  def compose_dm(recipient, subject, body):
@@ -1859,9 +1952,18 @@ def compose_dm(recipient, subject, body):
1859
1952
  return {"ok": True, "thread_url": page.url}
1860
1953
 
1861
1954
  finally:
1862
- page.context.close()
1955
+ try:
1956
+ if is_cdp:
1957
+ page.close()
1958
+ else:
1959
+ page.context.close()
1960
+ except Exception:
1961
+ pass
1863
1962
  if not is_cdp:
1864
- browser.close()
1963
+ try:
1964
+ browser.close()
1965
+ except Exception:
1966
+ pass
1865
1967
 
1866
1968
 
1867
1969
  def scrape_views(username, max_scrolls=300):
@@ -2022,9 +2124,18 @@ def scrape_views(username, max_scrolls=300):
2022
2124
  except Exception as e:
2023
2125
  return {"ok": False, "error": str(e)}
2024
2126
  finally:
2025
- page.context.close()
2127
+ try:
2128
+ if is_cdp:
2129
+ page.close()
2130
+ else:
2131
+ page.context.close()
2132
+ except Exception:
2133
+ pass
2026
2134
  if not is_cdp:
2027
- browser.close()
2135
+ try:
2136
+ browser.close()
2137
+ except Exception:
2138
+ pass
2028
2139
 
2029
2140
 
2030
2141
  def main():