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
|
@@ -139,6 +139,47 @@ def _extract_github_user(items: list[dict]) -> str:
|
|
|
139
139
|
return max(counts, key=counts.get)
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
# Hosts that can never be a brand's own site; their presence in results is
|
|
143
|
+
# platform noise, not an official-domain signal.
|
|
144
|
+
_PLATFORM_HOSTS = {
|
|
145
|
+
"reddit.com", "x.com", "twitter.com", "github.com", "youtube.com",
|
|
146
|
+
"facebook.com", "instagram.com", "tiktok.com", "linkedin.com",
|
|
147
|
+
"wikipedia.org", "medium.com", "trustpilot.com", "crunchbase.com",
|
|
148
|
+
"bloomberg.com", "apple.com", "play.google.com", "google.com",
|
|
149
|
+
"threads.com", "pinterest.com", "glassdoor.com", "indeed.com",
|
|
150
|
+
"news.ycombinator.com", "substack.com", "amazon.com", "ebay.com",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _extract_official_domain(topic: str, items: list[dict]) -> str:
|
|
155
|
+
"""Extract the brand's own domain from search-result URLs.
|
|
156
|
+
|
|
157
|
+
Conservative on purpose: only a hostname whose registrable label
|
|
158
|
+
normalizes to the topic name qualifies ("ThriftBooks" -> thriftbooks.com).
|
|
159
|
+
This feeds Trustpilot targeting as a HINT (the engine retries via the
|
|
160
|
+
CLI's own search when the hint misses), so a miss here is cheap and a
|
|
161
|
+
wrong guess is recoverable.
|
|
162
|
+
"""
|
|
163
|
+
want = re.sub(r"[^a-z0-9]", "", topic.lower())
|
|
164
|
+
if not want:
|
|
165
|
+
return ""
|
|
166
|
+
host_pattern = re.compile(r"https?://([A-Za-z0-9.-]+)")
|
|
167
|
+
for item in items:
|
|
168
|
+
for source in [item.get("url", ""), f"{item.get('title', '')} {item.get('snippet', '')}"]:
|
|
169
|
+
for host in host_pattern.findall(source):
|
|
170
|
+
host = host.lower().strip(".")
|
|
171
|
+
bare = host.removeprefix("www.")
|
|
172
|
+
if any(bare == p or bare.endswith("." + p) for p in _PLATFORM_HOSTS):
|
|
173
|
+
continue
|
|
174
|
+
labels = bare.split(".")
|
|
175
|
+
if len(labels) < 2:
|
|
176
|
+
continue
|
|
177
|
+
registrable = labels[-2] if labels[-2] not in ("co", "com") or len(labels) < 3 else labels[-3]
|
|
178
|
+
if re.sub(r"[^a-z0-9]", "", registrable) == want:
|
|
179
|
+
return bare
|
|
180
|
+
return ""
|
|
181
|
+
|
|
182
|
+
|
|
142
183
|
def _extract_github_repos(items: list[dict]) -> list[str]:
|
|
143
184
|
"""Extract owner/repo strings from search results."""
|
|
144
185
|
repo_pattern = re.compile(r"github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)")
|
|
@@ -280,6 +321,7 @@ def auto_resolve(topic: str, config: dict) -> dict:
|
|
|
280
321
|
"x_handle": "",
|
|
281
322
|
"github_user": "",
|
|
282
323
|
"github_repos": [],
|
|
324
|
+
"trustpilot_domain": "",
|
|
283
325
|
"context": "",
|
|
284
326
|
"category": None,
|
|
285
327
|
"searches_run": 0,
|
|
@@ -329,16 +371,23 @@ def auto_resolve(topic: str, config: dict) -> dict:
|
|
|
329
371
|
github_user = _extract_github_user(results.get("github", []))
|
|
330
372
|
github_repos = canonicalize_github_repos(topic, _extract_github_repos(results.get("github", [])))
|
|
331
373
|
context = _build_context_summary(results.get("news", []))
|
|
374
|
+
# Official-site domain doubles as the Trustpilot targeting hint (review
|
|
375
|
+
# pages are keyed by domain). Scan news first (official sites surface in
|
|
376
|
+
# coverage), then the handle query's profile-adjacent results.
|
|
377
|
+
trustpilot_domain = _extract_official_domain(
|
|
378
|
+
topic, (results.get("news") or []) + (results.get("x_handle") or [])
|
|
379
|
+
)
|
|
332
380
|
|
|
333
381
|
subreddits, category = _merge_category_peers(topic, subreddits)
|
|
334
382
|
|
|
335
|
-
_log(f"Resolved {len(subreddits)} subreddits, x_handle={x_handle!r}, github_user={github_user!r}, github_repos={github_repos!r}, context_len={len(context)}, category={category!r}")
|
|
383
|
+
_log(f"Resolved {len(subreddits)} subreddits, x_handle={x_handle!r}, github_user={github_user!r}, github_repos={github_repos!r}, trustpilot_domain={trustpilot_domain!r}, context_len={len(context)}, category={category!r}")
|
|
336
384
|
|
|
337
385
|
return {
|
|
338
386
|
"subreddits": subreddits,
|
|
339
387
|
"x_handle": x_handle,
|
|
340
388
|
"github_user": github_user,
|
|
341
389
|
"github_repos": github_repos,
|
|
390
|
+
"trustpilot_domain": trustpilot_domain,
|
|
342
391
|
"context": context,
|
|
343
392
|
"category": category,
|
|
344
393
|
"searches_run": searches_run,
|
|
@@ -14,6 +14,8 @@ import struct
|
|
|
14
14
|
import sys
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
+
from .cookie_paths import path_exists
|
|
18
|
+
|
|
17
19
|
# Mac epoch: 2001-01-01 00:00:00 UTC (not used for filtering, but documented)
|
|
18
20
|
_MAC_EPOCH_OFFSET = 978307200 # seconds between Unix epoch and Mac epoch
|
|
19
21
|
|
|
@@ -98,7 +100,10 @@ def extract_safari_cookies_macos(
|
|
|
98
100
|
Extract cookies from Safari on macOS.
|
|
99
101
|
|
|
100
102
|
Args:
|
|
101
|
-
domain:
|
|
103
|
+
domain: Registrable host to match, with or without a leading dot
|
|
104
|
+
(e.g. "x.com" or ".x.com"). A stored cookie host matches when it
|
|
105
|
+
equals the domain or is a subdomain of it; unrelated hosts that
|
|
106
|
+
merely contain the text (e.g. "x.com.evil.tld") do not.
|
|
102
107
|
cookie_names: List of cookie names to extract (e.g. ["auth_token", "ct0"])
|
|
103
108
|
|
|
104
109
|
Returns:
|
|
@@ -118,24 +123,47 @@ def extract_safari_cookies_macos(
|
|
|
118
123
|
/ "Cookies.binarycookies",
|
|
119
124
|
Path.home() / "Library" / "Cookies" / "Cookies.binarycookies",
|
|
120
125
|
]
|
|
121
|
-
|
|
126
|
+
denied = None
|
|
127
|
+
partial = None
|
|
128
|
+
for cookie_path in cookie_paths:
|
|
129
|
+
try:
|
|
130
|
+
if not path_exists(cookie_path):
|
|
131
|
+
continue
|
|
132
|
+
raw = cookie_path.read_bytes()
|
|
133
|
+
except PermissionError as exc:
|
|
134
|
+
if denied is None:
|
|
135
|
+
denied = exc
|
|
136
|
+
continue
|
|
137
|
+
except OSError:
|
|
138
|
+
continue
|
|
139
|
+
result = _parse_binary_cookies(raw, domain, cookie_names)
|
|
140
|
+
if result and all(result.get(name) for name in cookie_names):
|
|
141
|
+
return result
|
|
142
|
+
if result and partial is None:
|
|
143
|
+
partial = result
|
|
122
144
|
|
|
123
|
-
|
|
124
|
-
raw = cookie_path.read_bytes()
|
|
125
|
-
except FileNotFoundError:
|
|
126
|
-
return None
|
|
127
|
-
except PermissionError:
|
|
145
|
+
if denied is not None:
|
|
128
146
|
print(
|
|
129
147
|
"[safari] Permission denied reading Cookies.binarycookies. "
|
|
130
|
-
"
|
|
131
|
-
"Privacy & Security
|
|
148
|
+
"Check the invoking app's browser-data permissions in System Settings > "
|
|
149
|
+
"Privacy & Security, then retry setup.",
|
|
132
150
|
file=sys.stderr,
|
|
133
151
|
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
152
|
+
raise denied
|
|
153
|
+
return partial
|
|
154
|
+
|
|
137
155
|
|
|
138
|
-
|
|
156
|
+
def _host_matches(stored_host: str, domain: str) -> bool:
|
|
157
|
+
"""True when stored_host equals domain or is a subdomain of it.
|
|
158
|
+
|
|
159
|
+
Safari stores domain cookies with a leading dot (".x.com") and host-only
|
|
160
|
+
cookies without one ("x.com"); both spellings are accepted on either side.
|
|
161
|
+
"""
|
|
162
|
+
host = stored_host.strip().lstrip(".").lower()
|
|
163
|
+
wanted = domain.strip().lstrip(".").lower()
|
|
164
|
+
if not host or not wanted:
|
|
165
|
+
return False
|
|
166
|
+
return host == wanted or host.endswith("." + wanted)
|
|
139
167
|
|
|
140
168
|
|
|
141
169
|
def _parse_binary_cookies(
|
|
@@ -182,8 +210,7 @@ def _parse_binary_cookies(
|
|
|
182
210
|
page_data = raw[offset : offset + ps]
|
|
183
211
|
cookies = _parse_page(page_data)
|
|
184
212
|
for c in cookies:
|
|
185
|
-
|
|
186
|
-
if domain in c["url"] and c["name"] in names_set:
|
|
213
|
+
if _host_matches(c["url"], domain) and c["name"] in names_set:
|
|
187
214
|
result[c["name"]] = c["value"]
|
|
188
215
|
offset += ps
|
|
189
216
|
|