socialmag v0.1 — 4/5 platforms live (Threads, FB, IG, TikTok)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""YouTube community post flow.
|
||||
|
||||
1. navigate youtube.com
|
||||
2. click button[aria-label="Create"]
|
||||
3. click "Create post" in the dropdown
|
||||
4. wait for the redirect to the channel posts page
|
||||
5. type into #contenteditable-root
|
||||
6. click Post
|
||||
7. the editor emptying / the post appearing = success
|
||||
|
||||
Community posts require a channel that has them enabled. Video upload
|
||||
(post_youtube_video in the proposal) is NOT built here — the brief scoped this
|
||||
module to the community post.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from ..cdp import CDPError, human_delay
|
||||
from . import guard, require_text, validate_media
|
||||
from ..selectors import YOUTUBE as S
|
||||
|
||||
PLATFORM = "yt"
|
||||
|
||||
|
||||
async def post(cdp_client, text, media_path=None, options=None):
|
||||
options = options or {}
|
||||
require_text(PLATFORM, text)
|
||||
validate_media(media_path)
|
||||
if media_path:
|
||||
# Community posts do take images, but that flow is unmapped. Refuse
|
||||
# rather than drop the media on the floor.
|
||||
raise CDPError(
|
||||
"yt: media on community posts is not implemented (the image-attach "
|
||||
"flow has not been mapped live). Post text-only, or use a platform "
|
||||
"whose media flow is built."
|
||||
)
|
||||
|
||||
tab, reused = await cdp_client.get_or_open_tab(S["url"], "youtube.com")
|
||||
if reused:
|
||||
await tab.eval("window.onbeforeunload = null")
|
||||
await tab.navigate(S["url"])
|
||||
return await guard(PLATFORM, tab, _flow(tab, text))
|
||||
|
||||
|
||||
async def _flow(tab, text):
|
||||
await tab.wait_for(S["create_button"])
|
||||
await human_delay()
|
||||
await tab.click(S["create_button"])
|
||||
|
||||
# TODO: VALIDATE LIVE — the dropdown item text and element type.
|
||||
await _wait_for_js(tab, S["create_post_item_js"], "the 'Create post' menu item")
|
||||
await tab.click_js(S["create_post_item_js"])
|
||||
|
||||
# Step 4: YouTube navigates to the channel's posts page.
|
||||
await tab.wait_for_load(timeout=30.0)
|
||||
await tab.wait_for(S["editor"], timeout=30.0)
|
||||
|
||||
# #contenteditable-root is a YouTube custom editor: it listens to real key
|
||||
# events, so type per-char rather than insertText.
|
||||
# TODO: VALIDATE LIVE — whether insertText alone enables the Post button.
|
||||
await tab.type_text(S["editor"], text, per_char=True)
|
||||
|
||||
if await tab.exists(S["post_button"]):
|
||||
await tab.click(S["post_button"])
|
||||
else:
|
||||
await tab.click_js(S["post_button_js"])
|
||||
|
||||
# TODO: VALIDATE LIVE — success marker. The editor going empty is the
|
||||
# cheapest observable signal; the proposal says "post appears".
|
||||
await _wait_for_editor_empty(tab)
|
||||
|
||||
|
||||
async def _wait_for_js(tab, expression, what, timeout=20.0):
|
||||
deadline = asyncio.get_running_loop().time() + timeout
|
||||
while asyncio.get_running_loop().time() < deadline:
|
||||
if await tab.eval("!!(%s)" % expression):
|
||||
return
|
||||
await asyncio.sleep(0.25)
|
||||
raise CDPError(f"yt: {what} did not appear within {timeout}s")
|
||||
|
||||
|
||||
async def _wait_for_editor_empty(tab, timeout=30.0):
|
||||
deadline = asyncio.get_running_loop().time() + timeout
|
||||
while asyncio.get_running_loop().time() < deadline:
|
||||
text = await tab.eval(
|
||||
"(() => { const e = document.querySelector('%s');"
|
||||
" return e ? e.innerText.trim() : null; })()" % S["editor"]
|
||||
)
|
||||
if text == "":
|
||||
return
|
||||
await asyncio.sleep(0.5)
|
||||
raise CDPError("yt: editor still holds text 30s after clicking Post")
|
||||
|
||||
|
||||
async def check_login(cdp_client):
|
||||
tab, reused = await cdp_client.get_or_open_tab(S["url"], "youtube.com")
|
||||
if reused:
|
||||
await tab.eval("window.onbeforeunload = null")
|
||||
await tab.navigate(S["url"])
|
||||
await asyncio.sleep(5)
|
||||
if await tab.exists(S["login_marker"]):
|
||||
return False
|
||||
return await tab.eval(
|
||||
"!!document.querySelector('#avatar-btn, button#avatar-btn,"
|
||||
" ytd-topbar-menu-button-renderer #avatar-btn')"
|
||||
)
|
||||
Reference in New Issue
Block a user