Skip to content
Open
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
3 changes: 1 addition & 2 deletions src/chatgpt_web2api/backend_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ async def _fetch_recent_conversation_projection(
on 401 (with breaker trip), ``_Transient404`` on 404, ``RuntimeError``
on other non-OK status, and ``CDPJSError`` on transport failure.
"""
from .backend_projection import CONVERSATION_PROJECTION_JS, TURN_PROJECTION_LIMIT
from .backend_projection import CONVERSATION_PROJECTION_JS
from .cdp_driver import AuthExpiredError, CDPJSError

d = self._driver
Expand All @@ -286,7 +286,6 @@ async def _fetch_recent_conversation_projection(
{
"conv_id": conversation_id,
"token": d._access_token,
"limit": TURN_PROJECTION_LIMIT,
},
timeout=15,
)
Expand Down
2 changes: 1 addition & 1 deletion src/chatgpt_web2api/backend_projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
CONVERSATION_PROJECTION_JS = """
(async function() {
try {
var r = await fetch('/backend-api/conversation/' + __D.conv_id + '?offset=0&limit=' + __D.limit, {
var r = await fetch('/backend-api/conversation/' + __D.conv_id, {
headers: {'Authorization': 'Bearer ' + __D.token}
});
if (!r.ok) return JSON.stringify({__status: r.status});
Expand Down
6 changes: 3 additions & 3 deletions src/chatgpt_web2api/cdp_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -1499,12 +1499,12 @@ async def _read_assistant_count_baseline(self) -> int:

selector = (
"document.querySelectorAll("
"'[data-message-author-role=\"assistant\"]'"
"'[data-message-author-role=\"assistant\"], [data-content-search-unit-key$=\":assistant\"], [data-chatgpt-search-unit-key$=\":assistant\"]'"
").length"
)
user_selector = (
"document.querySelectorAll("
"'[data-message-author-role=\"user\"]'"
"'[data-message-author-role=\"user\"], [data-content-search-unit-key$=\":user\"], [data-chatgpt-search-unit-key$=\":user\"]'"
").length"
)
max_attempts = 3
Expand Down Expand Up @@ -1610,7 +1610,7 @@ async def _verify_send_acknowledged(self) -> bool | None:
result = await self._js_strict(
"(function() {"
" var userMsgs = document.querySelectorAll("
" '[data-message-author-role=\"user\"]').length;"
" '[data-message-author-role=\"user\"], [data-content-search-unit-key$=\":user\"], [data-chatgpt-search-unit-key$=\":user\"]').length;"
f" var composer = document.querySelector('{COMPOSER_SELECTOR}')"
f" || document.querySelector('{COMPOSER_FALLBACK_SELECTOR}');"
" var composerPresent = !!composer;"
Expand Down
4 changes: 2 additions & 2 deletions src/chatgpt_web2api/completion_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ async def stream_until_complete(

try:
raw = await d._js_strict(
"document.querySelectorAll('[data-message-author-role=\"assistant\"]').length"
"document.querySelectorAll('[data-message-author-role=\"assistant\"], [data-content-search-unit-key$=\":assistant\"], [data-chatgpt-search-unit-key$=\":assistant\"]').length"
)
current_count = int(raw or 0)
except CDPJSError:
Expand Down Expand Up @@ -474,7 +474,7 @@ async def stream_until_complete(
try:
result = await d._js_strict(
"(function() {"
" var msgs = document.querySelectorAll('[data-message-author-role=\"assistant\"]');"
" var msgs = document.querySelectorAll('[data-message-author-role=\"assistant\"], [data-content-search-unit-key$=\":assistant\"], [data-chatgpt-search-unit-key$=\":assistant\"]');"
" if (!msgs.length) return JSON.stringify({text:'', md_text:'', html_len:0, child_count:0, has_action:false, is_thinking:false});"
" var last = msgs[msgs.length - 1];"
# Text: the clean answer lives in ``.markdown`` textContent.
Expand Down
6 changes: 3 additions & 3 deletions tests/test_backend_projection_js.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ def test_js_is_nonempty_string(self):

def test_js_fetches_correct_endpoint(self):
assert "/backend-api/conversation/" in CONVERSATION_PROJECTION_JS
assert "offset=0" in CONVERSATION_PROJECTION_JS
assert "limit=" in CONVERSATION_PROJECTION_JS
assert "?offset=" not in CONVERSATION_PROJECTION_JS
assert "&limit=" not in CONVERSATION_PROJECTION_JS

def test_js_threads_data_slots(self):
assert "__D.conv_id" in CONVERSATION_PROJECTION_JS
assert "__D.token" in CONVERSATION_PROJECTION_JS
assert "__D.limit" in CONVERSATION_PROJECTION_JS
assert "__D.limit" not in CONVERSATION_PROJECTION_JS

def test_js_status_decode(self):
assert "__status" in CONVERSATION_PROJECTION_JS
Expand Down
9 changes: 9 additions & 0 deletions tests/test_current_dom_role_selectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import inspect
from chatgpt_web2api.cdp_driver import CDPDriver
from chatgpt_web2api.completion_detector import CompletionDetector

def test_current_chatgpt_role_selectors_are_supported():
src = inspect.getsource(CDPDriver) + inspect.getsource(CompletionDetector)
assert "data-content-search-unit-key$=\\\":assistant\\\"" in src

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] New selector test asserts on source text rather than behavior

tests/test_current_dom_role_selectors.py uses inspect.getsource() and substring matching to verify the new selectors exist in CDPDriver/CompletionDetector source. This is a valid smoke guard against revert regressions, but it is brittle in both directions: (1) it passes even if the selectors appear in dead code, comments, or malformed JS that never executes, and (2) it will spuriously fail if the JS string quoting/escaping is ever reformatted (e.g., switching to single-quoted attribute selectors), even when behavior is unchanged. Since the selectors are static strings with no runtime injection surface, a behavioral test (e.g., running the compiled JS against a small fixture DOM via the existing CDP test harness/mocks) would pin actual querySelectorAll semantics. Acceptable to merge as-is for a live hotfix, but consider tightening later.

assert "data-content-search-unit-key$=\\\":user\\\"" in src
assert "data-message-author-role" in src