luckiest-co 1.0.47 → 1.0.49
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/ab-testing/CHANGELOG.md +5 -0
- package/skills/ab-testing/SKILL.md +12 -9
- package/skills/ab-testing/evals/evals.json +18 -7
- package/skills/ab-testing/references/sample-size-guide.md +2 -2
- package/skills/ads/CHANGELOG.md +5 -0
- package/skills/ads/SKILL.md +4 -4
- package/skills/ai-seo/CHANGELOG.md +7 -0
- package/skills/ai-seo/SKILL.md +27 -20
- package/skills/analytics/CHANGELOG.md +6 -0
- package/skills/analytics/SKILL.md +4 -3
- package/skills/analytics/evals/evals.json +27 -0
- package/skills/analytics/references/ga4-implementation.md +7 -3
- package/skills/analytics/references/gtm-implementation.md +8 -2
- package/skills/aso/CHANGELOG.md +5 -0
- package/skills/aso/SKILL.md +4 -4
- package/skills/aso/evals/evals.json +15 -2
- package/skills/aso/references/benchmarks.md +4 -2
- package/skills/aso/references/google-play-specs.md +5 -1
- package/skills/brainstorm/SKILL.md +59 -16
- package/skills/brainstorm/builder-check-prompt.md +23 -0
- package/skills/churn-prevention/CHANGELOG.md +5 -0
- package/skills/churn-prevention/SKILL.md +3 -3
- package/skills/churn-prevention/evals/evals.json +14 -0
- package/skills/churn-prevention/references/dunning-playbook.md +6 -1
- package/skills/coder-brainstorming/CHANGELOG.md +10 -0
- package/skills/coder-brainstorming/SKILL.md +62 -20
- package/skills/coder-brainstorming/builder-check-prompt.md +23 -0
- package/skills/coder-brainstorming/scripts/server.cjs +1 -1
- package/skills/coder-debugging/CHANGELOG.md +4 -0
- package/skills/coder-debugging/SKILL.md +5 -5
- package/skills/coder-requesting-code-review/CHANGELOG.md +4 -0
- package/skills/coder-requesting-code-review/SKILL.md +4 -4
- package/skills/coder-subagent-driven-development/CHANGELOG.md +4 -0
- package/skills/coder-subagent-driven-development/SKILL.md +3 -3
- package/skills/coder-subagent-driven-development/scripts/sdd-workspace +3 -1
- package/skills/cold-email/CHANGELOG.md +7 -0
- package/skills/cold-email/SKILL.md +27 -7
- package/skills/cold-email/evals/evals.json +30 -0
- package/skills/cold-email/references/benchmarks.md +15 -11
- package/skills/cold-email/references/deliverability.md +110 -0
- package/skills/cold-email/references/linkedin-outreach.md +91 -0
- package/skills/cold-email/references/multichannel-cadence.md +79 -0
- package/skills/cold-email/references/outbound-plays.md +88 -0
- package/skills/cold-email/references/personalization.md +11 -0
- package/skills/cold-email/references/reply-handling.md +65 -0
- package/skills/competitor-profiling/CHANGELOG.md +5 -0
- package/skills/competitor-profiling/SKILL.md +4 -4
- package/skills/content-strategy/CHANGELOG.md +5 -0
- package/skills/content-strategy/SKILL.md +5 -3
- package/skills/copy-editing/CHANGELOG.md +5 -0
- package/skills/copy-editing/SKILL.md +5 -5
- package/skills/copy-editing/references/content-refresh.md +33 -0
- package/skills/cro/CHANGELOG.md +5 -0
- package/skills/cro/SKILL.md +14 -3
- package/skills/cro/references/form.md +1 -1
- package/skills/directory-submissions/CHANGELOG.md +5 -0
- package/skills/directory-submissions/SKILL.md +13 -4
- package/skills/directory-submissions/evals/evals.json +15 -1
- package/skills/directory-submissions/references/directory-list.md +2 -4
- package/skills/emails/CHANGELOG.md +5 -0
- package/skills/emails/SKILL.md +14 -4
- package/skills/launch/CHANGELOG.md +7 -0
- package/skills/launch/SKILL.md +8 -4
- package/skills/launch/evals/evals.json +29 -0
- package/skills/launch/references/shipped-changes.md +84 -0
- package/skills/launch/references/site-launch-qa.md +55 -0
- package/skills/lead-magnets/CHANGELOG.md +5 -0
- package/skills/lead-magnets/SKILL.md +6 -4
- package/skills/lead-magnets/evals/evals.json +18 -0
- package/skills/lead-magnets/references/content-to-skill-corpus-design.md +89 -0
- package/skills/lead-magnets/references/content-to-skill-release.md +71 -0
- package/skills/lead-magnets/references/content-to-skill.md +108 -0
- package/skills/marketing-plan/CHANGELOG.md +5 -0
- package/skills/marketing-plan/SKILL.md +3 -3
- package/skills/marketing-plan/evals/evals.json +13 -0
- package/skills/marketing-plan/references/budget-planning.md +26 -17
- package/skills/model-router/CHANGELOG.md +6 -0
- package/skills/model-router/SKILL.md +3 -3
- package/skills/model-router/scripts/route.py +1 -1
- package/skills/model-router/scripts/test_route.py +5 -0
- package/skills/pricing/CHANGELOG.md +5 -0
- package/skills/pricing/SKILL.md +4 -4
- package/skills/pricing/evals/evals.json +12 -0
- package/skills/pricing/references/research-methods.md +8 -4
- package/skills/programmatic-seo/CHANGELOG.md +5 -0
- package/skills/programmatic-seo/SKILL.md +8 -4
- package/skills/programmatic-seo/evals/evals.json +13 -0
- package/skills/programmatic-seo/references/implementation-platforms.md +37 -0
- package/skills/prospecting/CHANGELOG.md +7 -0
- package/skills/prospecting/SKILL.md +27 -13
- package/skills/prospecting/evals/evals.json +41 -0
- package/skills/prospecting/references/account-research.md +59 -0
- package/skills/prospecting/references/b2b-prospecting.md +1 -1
- package/skills/prospecting/references/compliance.md +44 -6
- package/skills/prospecting/references/data-sources.md +13 -15
- package/skills/prospecting/references/enrichment-playbook.md +61 -0
- package/skills/prospecting/references/saas-prospecting.md +1 -1
- package/skills/prospecting/references/signal-plays.md +99 -0
- package/skills/prospecting/references/sourcing-and-enrichment.md +89 -0
- package/skills/referrals/CHANGELOG.md +5 -0
- package/skills/referrals/SKILL.md +10 -4
- package/skills/referrals/evals/evals.json +99 -0
- package/skills/referrals/references/affiliate-operations.md +64 -0
- package/skills/referrals/references/affiliate-programs.md +2 -2
- package/skills/referrals/references/program-examples.md +14 -6
- package/skills/referrals/references/review-platform-rules.md +36 -0
- package/skills/referrals/references/review-program-operations.md +62 -0
- package/skills/referrals/references/review-programs.md +96 -0
- package/skills/revops/CHANGELOG.md +5 -0
- package/skills/revops/SKILL.md +10 -6
- package/skills/revops/evals/evals.json +13 -0
- package/skills/revops/references/automation-playbooks.md +1 -1
- package/skills/revops/references/inbound-routing.md +43 -0
- package/skills/revops/references/lifecycle-definitions.md +31 -0
- package/skills/sales-enablement/CHANGELOG.md +5 -0
- package/skills/sales-enablement/SKILL.md +6 -4
- package/skills/sales-enablement/evals/evals.json +14 -0
- package/skills/sales-enablement/references/cold-call-scripts.md +68 -0
- package/skills/schema/CHANGELOG.md +5 -0
- package/skills/schema/SKILL.md +20 -8
- package/skills/schema/evals/evals.json +40 -0
- package/skills/schema/references/schema-examples.md +9 -15
- package/skills/seo-audit/CHANGELOG.md +5 -0
- package/skills/seo-audit/SKILL.md +10 -16
- package/skills/seo-audit/evals/evals.json +68 -0
- package/skills/seo-audit/references/international-seo.md +9 -11
- package/skills/seo-audit/references/local-seo-pages-and-measurement.md +39 -0
- package/skills/seo-audit/references/local-seo-profile-and-citations.md +41 -0
- package/skills/seo-audit/references/local-seo.md +86 -0
- package/skills/seo-audit/references/rankings-push.md +62 -0
- package/skills/seo-audit/references/title-tags.md +89 -0
- package/skills/signup/CHANGELOG.md +5 -0
- package/skills/signup/SKILL.md +7 -3
- package/skills/site-architecture/CHANGELOG.md +5 -0
- package/skills/site-architecture/SKILL.md +5 -4
- package/skills/site-architecture/evals/evals.json +25 -0
- package/skills/site-architecture/references/platforms-and-migration.md +84 -0
- package/skills/social/CHANGELOG.md +5 -0
- package/skills/social/SKILL.md +4 -4
- package/skills/trends/.skillignore +3 -0
- package/skills/trends/CHANGELOG.md +20 -0
- package/skills/trends/SKILL.md +12 -8
- package/skills/trends/references/agent-mode.md +36 -0
- package/skills/trends/references/category-peers.md +38 -0
- package/skills/trends/references/comparison.md +227 -0
- package/skills/trends/references/competitors.md +29 -0
- package/skills/trends/references/discovery.md +90 -0
- package/skills/trends/references/engine-contract.md +111 -1889
- package/skills/trends/references/followups.md +112 -0
- package/skills/trends/references/grok-bot-x.md +55 -0
- package/skills/trends/references/hiring-signals.md +19 -0
- package/skills/trends/references/library-queue.md +33 -0
- package/skills/trends/references/recommendations.md +86 -0
- package/skills/trends/references/research-runbook.md +781 -0
- package/skills/trends/references/runtime.md +165 -0
- package/skills/trends/references/save-html-brief.md +39 -20
- package/skills/trends/references/setup-wizard.md +253 -0
- package/skills/trends/references/synthesis.md +289 -0
- package/skills/trends/scripts/box_chrome_login.py +209 -0
- package/skills/trends/scripts/briefing.py +9 -17
- package/skills/trends/scripts/last30days.py +3247 -280
- package/skills/trends/scripts/lib/agentcookie.py +196 -0
- package/skills/trends/scripts/lib/amazon.py +877 -0
- package/skills/trends/scripts/lib/arxiv.py +42 -8
- package/skills/trends/scripts/lib/backends.py +769 -0
- package/skills/trends/scripts/lib/bird_x.py +340 -57
- package/skills/trends/scripts/lib/bluesky.py +84 -10
- package/skills/trends/scripts/lib/bounded_get.py +156 -0
- package/skills/trends/scripts/lib/brightdata.py +291 -0
- package/skills/trends/scripts/lib/categories.py +7 -1
- package/skills/trends/scripts/lib/chrome_cdp.py +455 -0
- package/skills/trends/scripts/lib/chrome_cookies.py +132 -39
- package/skills/trends/scripts/lib/cluster.py +31 -54
- package/skills/trends/scripts/lib/competitors.py +10 -1
- package/skills/trends/scripts/lib/cookie_extract.py +221 -44
- package/skills/trends/scripts/lib/cookie_paths.py +27 -0
- package/skills/trends/scripts/lib/corpus.py +439 -0
- package/skills/trends/scripts/lib/devto.py +313 -0
- package/skills/trends/scripts/lib/discovery_handoff.py +988 -0
- package/skills/trends/scripts/lib/doctor.py +2166 -0
- package/skills/trends/scripts/lib/dripstack.py +203 -0
- package/skills/trends/scripts/lib/entity_extract.py +37 -0
- package/skills/trends/scripts/lib/env.py +1092 -138
- package/skills/trends/scripts/lib/fanout.py +11 -1
- package/skills/trends/scripts/lib/feed.py +61 -0
- package/skills/trends/scripts/lib/fetch_memo.py +135 -0
- package/skills/trends/scripts/lib/freshness.py +566 -0
- package/skills/trends/scripts/lib/fusion.py +314 -28
- package/skills/trends/scripts/lib/github.py +438 -24
- package/skills/trends/scripts/lib/grok_x.py +1237 -0
- package/skills/trends/scripts/lib/grounding.py +129 -11
- package/skills/trends/scripts/lib/hackernews.py +61 -4
- package/skills/trends/scripts/lib/health.py +373 -0
- package/skills/trends/scripts/lib/hosted.py +333 -0
- package/skills/trends/scripts/lib/html_publish.py +34 -0
- package/skills/trends/scripts/lib/html_render.py +169 -3
- package/skills/trends/scripts/lib/http.py +1215 -70
- package/skills/trends/scripts/lib/instagram.py +132 -9
- package/skills/trends/scripts/lib/jobs.py +4 -2
- package/skills/trends/scripts/lib/library.py +308 -0
- package/skills/trends/scripts/lib/library_index.py +477 -0
- package/skills/trends/scripts/lib/linkedin.py +271 -24
- package/skills/trends/scripts/lib/log.py +5 -2
- package/skills/trends/scripts/lib/meta_ads.py +982 -0
- package/skills/trends/scripts/lib/normalize.py +382 -28
- package/skills/trends/scripts/lib/parallel_mcp.py +234 -0
- package/skills/trends/scripts/lib/permission_preflight.py +42 -3
- package/skills/trends/scripts/lib/perplexity.py +1080 -468
- package/skills/trends/scripts/lib/pipeline.py +5079 -211
- package/skills/trends/scripts/lib/planner.py +396 -53
- package/skills/trends/scripts/lib/polymarket.py +285 -23
- package/skills/trends/scripts/lib/prescriptions.py +432 -0
- package/skills/trends/scripts/lib/private_files.py +21 -0
- package/skills/trends/scripts/lib/providers.py +231 -10
- package/skills/trends/scripts/lib/quality_nudge.py +122 -37
- package/skills/trends/scripts/lib/reddit.py +177 -12
- package/skills/trends/scripts/lib/reddit_arctic.py +146 -5
- package/skills/trends/scripts/lib/reddit_keyless.py +121 -60
- package/skills/trends/scripts/lib/reddit_listing.py +215 -14
- package/skills/trends/scripts/lib/reddit_public.py +12 -13
- package/skills/trends/scripts/lib/reddit_search.py +304 -0
- package/skills/trends/scripts/lib/reddit_shreddit.py +51 -7
- package/skills/trends/scripts/lib/registers.py +134 -0
- package/skills/trends/scripts/lib/relevance.py +17 -0
- package/skills/trends/scripts/lib/render.py +2161 -271
- package/skills/trends/scripts/lib/rerank.py +623 -55
- package/skills/trends/scripts/lib/resolve.py +50 -1
- package/skills/trends/scripts/lib/safari_cookies.py +42 -15
- package/skills/trends/scripts/lib/schema.py +782 -1
- package/skills/trends/scripts/lib/setup_wizard.py +779 -120
- package/skills/trends/scripts/lib/signals.py +206 -7
- package/skills/trends/scripts/lib/stocktwits.py +374 -0
- package/skills/trends/scripts/lib/subproc.py +307 -53
- package/skills/trends/scripts/lib/techmeme.py +89 -64
- package/skills/trends/scripts/lib/telegram.py +317 -0
- package/skills/trends/scripts/lib/tiktok.py +13 -9
- package/skills/trends/scripts/lib/topic_shape.py +340 -0
- package/skills/trends/scripts/lib/transcribe.py +12 -3
- package/skills/trends/scripts/lib/trustpilot.py +256 -5
- package/skills/trends/scripts/lib/truthsocial.py +9 -1
- package/skills/trends/scripts/lib/ui.py +6 -0
- package/skills/trends/scripts/lib/usage.py +132 -0
- package/skills/trends/scripts/lib/web_search_keyless.py +63 -4
- package/skills/trends/scripts/lib/windows_job.py +138 -0
- package/skills/trends/scripts/lib/x_api.py +824 -0
- package/skills/trends/scripts/lib/x_envelope.py +733 -0
- package/skills/trends/scripts/lib/x_judge.py +292 -0
- package/skills/trends/scripts/lib/xai_x.py +26 -3
- package/skills/trends/scripts/lib/xquik.py +89 -15
- package/skills/trends/scripts/lib/xurl_x.py +284 -91
- package/skills/trends/scripts/lib/youtube_yt.py +570 -119
- package/skills/trends/scripts/setup-keychain.sh +12 -4
- package/skills/trends/scripts/setup-pass.sh +3 -0
- package/skills/trends/scripts/store.py +444 -50
- package/skills/trends/scripts/watchlist.py +38 -11
- package/skills/trends/scripts/x_lane_parity.py +107 -0
- package/skills/video-studio/CHANGELOG.md +4 -0
- package/skills/video-studio/SKILL.md +4 -4
- package/skills/video-studio/references/styles/product-flight.md +45 -0
- package/skills/video-studio/references/styles.md +3 -1
- package/skills/video-studio-motion/ATTRIBUTION.md +1 -1
- package/skills/video-studio-motion/CHANGELOG.md +4 -0
- package/skills/video-studio-motion/SKILL.md +8 -5
- package/skills/youtube-planner/ATTRIBUTION.md +25 -0
- package/skills/youtube-planner/CHANGELOG.md +12 -0
- package/skills/youtube-planner/SKILL.md +191 -0
- package/skills/youtube-planner/references/plan-template.md +57 -0
- package/skills/youtube-planner/references/stages.md +119 -0
- package/skills/youtube-planner/scripts/floor_ceiling.py +132 -0
- package/skills/coder-brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/skills/trends/scripts/lib/reddit_rss.py +0 -224
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""agentcookie sidecar reader — an X cookie source for the bird backend.
|
|
2
|
+
|
|
3
|
+
``agentcookie`` is an external, user-installed CLI that can deliver browser
|
|
4
|
+
cookies on Linux (where Chrome's SQLite cookie store cannot be decrypted by
|
|
5
|
+
this engine's stdlib extractor). This module shells out to it and pulls the
|
|
6
|
+
``auth_token`` + ``ct0`` pair that the bird backend needs.
|
|
7
|
+
|
|
8
|
+
Deliberate constraints (see docs/plans/2026-08-31 X plan):
|
|
9
|
+
|
|
10
|
+
* **Soft dependency.** Activation is gated on ``shutil.which("agentcookie")``
|
|
11
|
+
resolving on the agent subprocess PATH, exactly like the other CLI-gated
|
|
12
|
+
optional sources (Digg, yt-dlp). A binary that is absent is not an error.
|
|
13
|
+
* **``AGENTCOOKIE=off`` disables it** regardless of PATH.
|
|
14
|
+
* **Independent of ``FROM_BROWSER``.** Reading the sidecar is not a browser
|
|
15
|
+
extraction, so it runs even when ``FROM_BROWSER`` is unset (the state in
|
|
16
|
+
which the in-process browser extractor stays off).
|
|
17
|
+
* We call ``agentcookie cookies --domain .x.com --json`` and parse its stdout.
|
|
18
|
+
We never open ``cookies-plain.db`` directly, never import an ``agentcookie``
|
|
19
|
+
Python package, and never add a Python dependency on it.
|
|
20
|
+
* Cookie **values are never logged**. Only counts / names are ever emitted.
|
|
21
|
+
* First complete pair wins: a lone ``auth_token`` or a lone ``ct0`` is not a
|
|
22
|
+
usable result — both must be present.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import shutil
|
|
30
|
+
import subprocess
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any, Dict, List, Optional
|
|
33
|
+
|
|
34
|
+
from . import log
|
|
35
|
+
|
|
36
|
+
AGENTCOOKIE_BIN = "agentcookie"
|
|
37
|
+
X_COOKIE_DOMAIN = ".x.com"
|
|
38
|
+
X_COOKIE_NAMES = ("auth_token", "ct0")
|
|
39
|
+
|
|
40
|
+
# agentcookie writes its config (including its role: "source" or "sink") here.
|
|
41
|
+
# A SINK receives cookies delivered from another machine; on Darwin, a sink is
|
|
42
|
+
# an "extra host" that gets the extra cookie lookups even though it is not a
|
|
43
|
+
# Mac mini. Reading this file is a plain filesystem read — NEVER a subprocess —
|
|
44
|
+
# so a MacBook (source/unknown role) never spawns agentcookie just to be
|
|
45
|
+
# classified (see the AE8 no-subprocess contract). Override the path for tests
|
|
46
|
+
# or non-default installs with AGENTCOOKIE_CONFIG.
|
|
47
|
+
_DEFAULT_CONFIG_PATH = Path.home() / ".config" / "agentcookie" / "config.json"
|
|
48
|
+
|
|
49
|
+
# The sidecar read shells out to another process; keep it bounded so a hung
|
|
50
|
+
# agentcookie never stalls config loading.
|
|
51
|
+
_TIMEOUT_SECONDS = 10
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _log(msg: str) -> None:
|
|
55
|
+
log.source_log("agentcookie", msg, tty_only=False)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_disabled(config: Optional[Dict[str, Any]] = None) -> bool:
|
|
59
|
+
"""True when ``AGENTCOOKIE=off`` in config/env disables the sidecar."""
|
|
60
|
+
raw = ""
|
|
61
|
+
if config is not None:
|
|
62
|
+
raw = config.get("AGENTCOOKIE") or ""
|
|
63
|
+
if not raw:
|
|
64
|
+
from . import env
|
|
65
|
+
|
|
66
|
+
raw = env.read_secret_env("AGENTCOOKIE") or ""
|
|
67
|
+
return str(raw).strip().lower() == "off"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_available(config: Optional[Dict[str, Any]] = None) -> bool:
|
|
71
|
+
"""True when the sidecar could be used: on PATH and not disabled.
|
|
72
|
+
|
|
73
|
+
PATH-only, side-effect free (no subprocess). Safe for the doctor /
|
|
74
|
+
preflight prediction path — it reads no cookies, only whether the binary
|
|
75
|
+
would be reachable at run time.
|
|
76
|
+
"""
|
|
77
|
+
if is_disabled(config):
|
|
78
|
+
return False
|
|
79
|
+
return shutil.which(AGENTCOOKIE_BIN) is not None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _config_path(config: Optional[Dict[str, Any]] = None) -> Path:
|
|
83
|
+
"""Path to agentcookie's config file (AGENTCOOKIE_CONFIG override wins)."""
|
|
84
|
+
override = ""
|
|
85
|
+
if config is not None:
|
|
86
|
+
override = config.get("AGENTCOOKIE_CONFIG") or ""
|
|
87
|
+
override = override or os.environ.get("AGENTCOOKIE_CONFIG") or ""
|
|
88
|
+
return Path(override) if override else _DEFAULT_CONFIG_PATH
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def role(config: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
|
92
|
+
"""agentcookie's configured role ("source"/"sink"), or None.
|
|
93
|
+
|
|
94
|
+
Reads and parses the config FILE only — no subprocess, so it is safe on a
|
|
95
|
+
MacBook that must not spawn agentcookie. Any failure (missing file, bad
|
|
96
|
+
JSON, no ``role`` key, wrong type) returns None. "parse failure = not sink".
|
|
97
|
+
"""
|
|
98
|
+
path = _config_path(config)
|
|
99
|
+
try:
|
|
100
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
101
|
+
except (OSError, ValueError):
|
|
102
|
+
return None
|
|
103
|
+
if not isinstance(data, dict):
|
|
104
|
+
return None
|
|
105
|
+
raw = data.get("role")
|
|
106
|
+
return raw.strip().lower() if isinstance(raw, str) else None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def role_is_sink(config: Optional[Dict[str, Any]] = None) -> bool:
|
|
110
|
+
"""True only when agentcookie's configured role parses as ``sink``."""
|
|
111
|
+
return role(config) == "sink"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _consume_cookie_list(items: List[Any], names: tuple, found: Dict[str, str]) -> None:
|
|
115
|
+
"""Collect ``name -> value`` for wanted cookie names from a cookie list."""
|
|
116
|
+
for item in items:
|
|
117
|
+
if not isinstance(item, dict):
|
|
118
|
+
continue
|
|
119
|
+
name = item.get("name")
|
|
120
|
+
value = item.get("value")
|
|
121
|
+
if name in names and isinstance(value, str) and value and name not in found:
|
|
122
|
+
found[name] = value
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _pair_from_json(data: Any, names: tuple) -> Dict[str, str]:
|
|
126
|
+
"""Extract wanted cookies from agentcookie JSON, tolerant of its shape.
|
|
127
|
+
|
|
128
|
+
Accepts a list of ``{"name","value",...}`` objects, a ``{"cookies": [...]}``
|
|
129
|
+
wrapper, or a flat ``{name: value}`` / ``{name: {"value": ...}}`` mapping.
|
|
130
|
+
"""
|
|
131
|
+
found: Dict[str, str] = {}
|
|
132
|
+
if isinstance(data, list):
|
|
133
|
+
_consume_cookie_list(data, names, found)
|
|
134
|
+
elif isinstance(data, dict):
|
|
135
|
+
cookies = data.get("cookies")
|
|
136
|
+
if isinstance(cookies, list):
|
|
137
|
+
_consume_cookie_list(cookies, names, found)
|
|
138
|
+
else:
|
|
139
|
+
for name in names:
|
|
140
|
+
raw = data.get(name)
|
|
141
|
+
if isinstance(raw, str) and raw:
|
|
142
|
+
found[name] = raw
|
|
143
|
+
elif isinstance(raw, dict) and isinstance(raw.get("value"), str) and raw["value"]:
|
|
144
|
+
found[name] = raw["value"]
|
|
145
|
+
return found
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def read_x_cookies(config: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, str]]:
|
|
149
|
+
"""Return the complete X cookie pair from agentcookie, or None.
|
|
150
|
+
|
|
151
|
+
Returns ``{"auth_token": ..., "ct0": ...}`` only when BOTH cookies are
|
|
152
|
+
present (no half-pair). Any failure (binary absent, disabled, non-zero
|
|
153
|
+
exit, unparsable JSON, timeout, incomplete pair) returns None so the
|
|
154
|
+
caller falls through to the next cookie source. Never raises.
|
|
155
|
+
"""
|
|
156
|
+
if is_disabled(config):
|
|
157
|
+
return None
|
|
158
|
+
binary = shutil.which(AGENTCOOKIE_BIN)
|
|
159
|
+
if binary is None:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
result = subprocess.run(
|
|
164
|
+
[binary, "cookies", "--domain", X_COOKIE_DOMAIN, "--json"],
|
|
165
|
+
capture_output=True,
|
|
166
|
+
text=True,
|
|
167
|
+
timeout=_TIMEOUT_SECONDS,
|
|
168
|
+
)
|
|
169
|
+
except subprocess.TimeoutExpired:
|
|
170
|
+
_log(f"timed out after {_TIMEOUT_SECONDS}s; skipping")
|
|
171
|
+
return None
|
|
172
|
+
except OSError as exc:
|
|
173
|
+
_log(f"could not run agentcookie: {type(exc).__name__}")
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
if result.returncode != 0:
|
|
177
|
+
# stderr may carry a reason; do not echo it (it can contain values).
|
|
178
|
+
_log(f"exited {result.returncode}; skipping")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
output = (result.stdout or "").strip()
|
|
182
|
+
if not output:
|
|
183
|
+
return None
|
|
184
|
+
try:
|
|
185
|
+
data = json.loads(output)
|
|
186
|
+
except json.JSONDecodeError:
|
|
187
|
+
_log("returned non-JSON output; skipping")
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
found = _pair_from_json(data, X_COOKIE_NAMES)
|
|
191
|
+
if all(name in found for name in X_COOKIE_NAMES):
|
|
192
|
+
_log(f"delivered a complete X cookie pair ({len(found)} of {len(X_COOKIE_NAMES)} names)")
|
|
193
|
+
return {name: found[name] for name in X_COOKIE_NAMES}
|
|
194
|
+
if found:
|
|
195
|
+
_log(f"returned an incomplete pair ({sorted(found)}); ignoring per no-half-pair rule")
|
|
196
|
+
return None
|