Skill: kay1003/kay-video-upload (version 1.0.0)

--- name: kay-video-upload description: 多平台短视频自动发布工具，支持抖音、视频号、快手、小红书、B站。当用户说"发布视频"、"上传视频到抖音/小红书/视频号/快手/B站"、"把视频发到所有平台"、"登录抖音"、"视频发布"等时使用此 skill。底层使用 Playwright + 本机 Chrome 操作各平台创作者后台完成自动发布，所有代码自包含在 scripts/ 目录，可直接复制到任意 OpenClaw 实例使用。 --- # video-publisher 完全自包含的多平台短视频发布 skill。所有代码在 `scripts/` 目录内，无外部依赖路径。 ## 目录结构 ``` scripts/ ├── publish.py # 统一入口（登录 + 发布） ├── setup.py # 首次安装向导（检测 Chrome、安装依赖） ├── conf.py # 配置（Chrome 路径由 setup.py 自动写入） ├── videos/ # 待发布视频放这里 ├── cookies/ # 各平台登录状态（自动生成） ├── uploader/ # 各平台发布模块（来自 SAU） │ ├── douyin_uploader/ │ ├── tencent_uploader/ │ ├── ks_uploader/ │ ├── xhs_uploader/ │ └── bilibili_uploader/ └── utils/ # 工具模块 ``` ## 视频目录规范 默认在 `scripts/videos/` 下放置视频，也可通过环境变量 `VIDEO_DIR` 指定其他目录： ```bash export VIDEO_DIR=/path/to/your/videos # Linux/macOS $env:VIDEO_DIR = "E:\openclaw\video" # Windows PowerShell ``` 视频文件格式： ``` 视频名.mp4 # 视频（必须） 视频名.txt # 第一行标题，第二行 #标签1 #标签2（必须） 视频名.png # 封面图（可选） ``` ## 首次安装 ```bash cd scripts/ python setup.py ``` 自动完成：检测 Chrome 路径、安装 playwright/biliup 等依赖、写入 conf.py。 ## 常用操作 ### 登录（每个平台首次或 cookie 过期时执行一次） ```bash python publish.py login douyin # 抖音（扫码） python publish.py login tencent # 视频号（微信扫码） python publish.py login ks # 快手（扫码） python publish.py login xhs # 小红书（手动登录后按 Enter） python publish.py login bilibili # B站（biliup 扫码） ``` ### 发布 ```bash python publish.py run douyin # 发布到抖音 python publish.py run tencent # 发布到视频号 python publish.py run ks # 发布到快手 python publish.py run xhs # 发布到小红书 python publish.py run bilibili # 发布到B站 python publish.py run all # 发布到所有已登录平台 ``` ## 通过 OpenClaw 使用 直接说： - "发布视频到抖音" - "把 videos 里的视频发到所有平台" - "抖音 cookie 过期了，重新登录" OpenClaw 会自动定位到 skill 的 `scripts/` 目录执行对应命令。 ## Agent 操作规范 1. 用户说"发布视频到 X 平台" 2. 检查 `scripts/cookies/<platform>_uploader/account.json` 是否存在 3. 不存在 → 执行 `python publish.py login <platform>`，告知用户扫码 4. 存在 → 执行 `python publish.py run <platform>` 5. 观察输出，完成后告知用户结果 执行命令时需先设置 Python 路径（Windows）： ```powershell $env:PATH = '<python_dir>;<python_scripts_dir>;' + $env:PATH python publish.py run douyin ``` 详细平台说明见 `references/platforms.md`

path: README.md
content: # kay-video-upload

多平台短视频自动发布工具，支持抖音、视频号、快手、小红书、B站。

## 特性

- 🚀 一键发布到 5 大平台
- 🔐 扫码登录，cookie 自动保存（有效期 30 天）
- 📁 完全自包含，无外部路径依赖
- 🎯 支持标题、标签、封面、定时发布
- 🛠️ 基于 Playwright + 本机 Chrome，稳定可靠

## 支持平台

| 平台 | 登录方式 | 定时发布 | 封面 | 商品链接 |
|------|----------|----------|------|----------|
| 抖音 | 扫码 | ✅ | ✅ | ✅ |
| 视频号 | 微信扫码 | ✅ | ✅ | ❌ |
| 快手 | 扫码 | ✅ | ❌ | ❌ |
| 小红书 | 手动登录 | ❌ | ✅ | ❌ |
| B站 | biliup 扫码 | ✅ | ❌ | ❌ |

## 快速开始

### 1. 安装

```bash
cd scripts/
python setup.py
```

自动完成：
- 检测本机 Chrome 路径
- 安装依赖（playwright、biliup 等）
- 配置 conf.py

### 2. 准备视频

在 `scripts/videos/` 目录（或通过环境变量 `VIDEO_DIR` 指定）下放置：

```
视频名.mp4       # 视频文件（必须）
视频名.txt       # 第一行标题，第二行 #标签1 #标签2（必须）
视频名.png       # 封面图（可选）
```

示例 `demo.txt`：
```
这是视频标题
#AI #科技 #教程
```

### 3. 登录平台

```bash
python publish.py login douyin    # 抖音
python publish.py login tencent   # 视频号
python publish.py login ks        # 快手
python publish.py login xhs       # 小红书
python publish.py login bilibili  # B站
```

每个平台只需登录一次，cookie 有效期约 30 天。

### 4. 发布视频

```bash
python publish.py run douyin      # 发布到抖音
python publish.py run all         # 发布到所有已登录平台
```

## 环境变量

| 变量 | 说明 | 默认值 |
|------|------|--------|
| `VIDEO_DIR` | 视频目录 | `scripts/videos/` |
| `CHROME_PATH` | Chrome 路径 | 自动检测 |
| `XHS_SERVER` | 小红书签名服务 | `http://127.0.0.1:11901` |

示例：
```bash
export VIDEO_DIR=/path/to/videos  # Linux/macOS
$env:VIDEO_DIR = "E:\videos"      # Windows PowerShell
```

## 在 OpenClaw 中使用

安装 skill 后，直接对话：

```
发布视频到抖音
把 videos 里的视频发到所有平台
抖音 cookie 过期了，重新登录
```

OpenClaw 会自动调用此 skill 完成操作。

## 注意事项

- 小红书需要额外的 XHS_SERVER 签名服务（可用 Docker 启动）
- 发布期间不要关闭自动弹出的 Chrome 窗口
- 定时发布默认从明天 16:00 开始，每天一条
- B站每条视频之间自动等待 30 秒（平台限速）

## 目录结构

```
scripts/
├── publish.py              # 统一入口
├── setup.py                # 安装向导
├── conf.py                 # 配置文件
├── videos/                 # 视频目录
├── cookies/                # 登录状态（自动生成）
├── uploader/               # 各平台模块
│   ├── douyin_uploader/
│   ├── tencent_uploader/
│   ├── ks_uploader/
│   ├── xhs_uploader/
│   └── bilibili_uploader/
└── utils/                  # 工具模块
```

## 依赖

- Python 3.10+
- playwright
- biliup
- loguru
- requests

由 `setup.py` 自动安装。

## License

MIT

## 致谢

基于 [social-auto-upload](https://github.com/dreammis/social-auto-upload) 的 uploader 模块构建。


path: scripts/conf.py
content: """
自动生成的 conf.py - 路径相对于本文件所在目录
"""
import os
from pathlib import Path

# BASE_DIR 指向 scripts/ 目录本身（所有资源相对于此）
BASE_DIR = Path(__file__).parent

# 视频目录 - 可通过环境变量 VIDEO_DIR 覆盖
VIDEO_DIR = Path(os.environ.get("VIDEO_DIR", str(BASE_DIR / "videos")))

# Chrome 路径 - 安装时由 setup.py 自动检测并写入，也可手动修改
LOCAL_CHROME_PATH = os.environ.get("CHROME_PATH", "")

# 是否无头模式（False = 显示浏览器窗口，登录时必须为 False）
LOCAL_CHROME_HEADLESS = False

# 小红书签名服务地址
XHS_SERVER = os.environ.get("XHS_SERVER", "http://127.0.0.1:11901")


path: scripts/publish.py
content: """
publish.py - 统一入口
用法：
    python publish.py login <platform>   # 登录平台
    python publish.py run <platform>     # 发布视频
    python publish.py check <platform>   # 检查 cookie 是否有效

platform: douyin | tencent | ks | xhs | bilibili | all
"""
import sys
import asyncio
import time
from pathlib import Path

# 确保 scripts/ 目录在 Python 路径中
SCRIPTS_DIR = Path(__file__).parent
if str(SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPTS_DIR))

from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS, VIDEO_DIR

# Cookie 目录
COOKIES = {
    "douyin":   SCRIPTS_DIR / "cookies" / "douyin_uploader" / "account.json",
    "tencent":  SCRIPTS_DIR / "cookies" / "tencent_uploader" / "account.json",
    "ks":       SCRIPTS_DIR / "cookies" / "ks_uploader" / "account.json",
    "xhs":      SCRIPTS_DIR / "cookies" / "xiaohongshu_uploader" / "account.json",
    "bilibili": SCRIPTS_DIR / "cookies" / "bilibili_uploader" / "account.json",
}

PLATFORMS = list(COOKIES.keys())


# ─── 视频目录解析 ────────────────────────────────────────────────────────────

def parse_video_dir(video_dir: Path):
    tasks = []
    for mp4 in sorted(video_dir.glob("*.mp4")):
        stem = mp4.stem
        txt = video_dir / f"{stem}.txt"
        cover = None
        for ext in (".png", ".jpg", ".jpeg"):
            c = video_dir / f"{stem}{ext}"
            if c.exists():
                cover = c
                break
        title, tags = stem, []
        if txt.exists():
            for line in txt.read_text(encoding="utf-8", errors="ignore").strip().splitlines():
                line = line.strip()
                if not line:
                    continue
                if line.startswith("#"):
                    tags += [t.lstrip("#").strip() for t in line.split() if t.startswith("#")]
                elif not title or title == stem:
                    title = line
        tasks.append({"mp4": mp4, "title": title, "tags": tags, "cover": cover})
    return tasks


# ─── 登录 ────────────────────────────────────────────────────────────────────

async def login_douyin():
    from uploader.douyin_uploader.main import douyin_setup
    print("[抖音] 开始登录...")
    return await douyin_setup(str(COOKIES["douyin"]), handle=True)


async def login_tencent():
    from uploader.tencent_uploader.main import weixin_setup
    print("[视频号] 开始登录...")
    return await weixin_setup(str(COOKIES["tencent"]), handle=True)


async def login_ks():
    from uploader.ks_uploader.main import ks_setup
    print("[快手] 开始登录...")
    return await ks_setup(str(COOKIES["ks"]), handle=True)


async def login_xhs():
    from playwright.async_api import async_playwright
    cookie_file = COOKIES["xhs"]
    print("[小红书] 请在弹出的浏览器中手动登录，登录完成后按 Enter...")
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            executable_path=LOCAL_CHROME_PATH, headless=False,
            args=["--disable-blink-features=AutomationControlled", "--lang=zh-CN", "--start-maximized"]
        )
        context = await browser.new_context()
        page = await context.new_page()
        await page.goto("https://www.xiaohongshu.com/explore")
        input("登录完成后按 Enter 继续...")
        await context.storage_state(path=str(cookie_file))
        await browser.close()
    print("[小红书] 登录状态已保存")
    return True


async def login_bilibili():
    import subprocess
    biliup = SCRIPTS_DIR / "uploader" / "bilibili_uploader" / "biliup.exe"
    cookie_file = COOKIES["bilibili"]
    cookie_file.parent.mkdir(parents=True, exist_ok=True)
    print(f"[B站] 运行 biliup 登录，cookie 保存到: {cookie_file}")
    subprocess.run([str(biliup), "-u", str(cookie_file), "login"], check=True)
    print("[B站] 登录完成")
    return True


LOGIN_FUNCS = {
    "douyin":   login_douyin,
    "tencent":  login_tencent,
    "ks":       login_ks,
    "xhs":      login_xhs,
    "bilibili": login_bilibili,
}


# ─── 发布 ────────────────────────────────────────────────────────────────────

async def publish_douyin(tasks):
    from uploader.douyin_uploader.main import douyin_setup, DouYinVideo
    from utils.files_times import generate_schedule_time_next_day
    cookie_file = str(COOKIES["douyin"])
    if not await douyin_setup(cookie_file, handle=False):
        print("[抖音] cookie 无效，请先运行: python publish.py login douyin")
        return
    times = generate_schedule_time_next_day(len(tasks), 1, daily_times=[16])
    for i, t in enumerate(tasks):
        print(f"[抖音] {t['mp4'].name} | {t['title']}")
        await DouYinVideo(t["title"], str(t["mp4"]), t["tags"], times[i], cookie_file,
                          thumbnail_path=str(t["cover"]) if t["cover"] else None).main()


async def publish_tencent(tasks):
    from uploader.tencent_uploader.main import weixin_setup, TencentVideo
    from utils.files_times import generate_schedule_time_next_day
    cookie_file = str(COOKIES["tencent"])
    if not await weixin_setup(cookie_file, handle=False):
        print("[视频号] cookie 无效，请先运行: python publish.py login tencent")
        return
    times = generate_schedule_time_next_day(len(tasks), 1, daily_times=[16])
    for i, t in enumerate(tasks):
        print(f"[视频号] {t['mp4'].name} | {t['title']}")
        await TencentVideo(t["title"], str(t["mp4"]), t["tags"], times[i], cookie_file,
                           thumbnail_path=str(t["cover"]) if t["cover"] else None).main()


async def publish_ks(tasks):
    from uploader.ks_uploader.main import ks_setup, KSVideo
    from utils.files_times import generate_schedule_time_next_day
    cookie_file = str(COOKIES["ks"])
    if not await ks_setup(cookie_file, handle=False):
        print("[快手] cookie 无效，请先运行: python publish.py login ks")
        return
    times = generate_schedule_time_next_day(len(tasks), 1, daily_times=[16])
    for i, t in enumerate(tasks):
        print(f"[快手] {t['mp4'].name} | {t['title']}")
        await KSVideo(t["title"], str(t["mp4"]), t["tags"], times[i], cookie_file).main()


async def publish_xhs(tasks):
    from uploader.xhs_uploader.main import XHSVideo
    cookie_file = str(COOKIES["xhs"])
    if not COOKIES["xhs"].exists():
        print("[小红书] cookie 不存在，请先运行: python publish.py login xhs")
        return
    for t in tasks:
        print(f"[小红书] {t['mp4'].name} | {t['title']}")
        await XHSVideo(t["title"], str(t["mp4"]), t["tags"], 0, cookie_file,
                       thumbnail_path=str(t["cover"]) if t["cover"] else None).main()


def publish_bilibili(tasks):
    from uploader.bilibili_uploader.main import (
        read_cookie_json_file, extract_keys_from_json, random_emoji, BilibiliUploader
    )
    from utils.files_times import generate_schedule_time_next_day
    from utils.constant import VideoZoneTypes
    cookie_file = COOKIES["bilibili"]
    if not cookie_file.exists():
        print("[B站] cookie 不存在，请先运行: python publish.py login bilibili")
        return
    cookie_data = extract_keys_from_json(read_cookie_json_file(cookie_file))
    times = generate_schedule_time_next_day(len(tasks), 1, daily_times=[16], timestamps=True)
    tid = VideoZoneTypes.KNOWLEDGE_SKILL.value  # 默认分区：野生技能协会，可按需修改
    for i, t in enumerate(tasks):
        title = t["title"] + random_emoji()
        print(f"[B站] {t['mp4'].name} | {title}")
        BilibiliUploader(cookie_data, t["mp4"], title, title, tid, t["tags"], times[i]).upload()
        time.sleep(30)  # B站限速


PUBLISH_FUNCS = {
    "douyin":   (publish_douyin,   True),   # (func, is_async)
    "tencent":  (publish_tencent,  True),
    "ks":       (publish_ks,       True),
    "xhs":      (publish_xhs,      True),
    "bilibili": (publish_bilibili, False),
}


# ─── 主入口 ──────────────────────────────────────────────────────────────────

def usage():
    print("用法:")
    print("  python publish.py login <platform>   # 登录")
    print("  python publish.py run <platform>     # 发布")
    print(f"  platform: {' | '.join(PLATFORMS)} | all")
    sys.exit(1)


async def run_login(platform: str):
    if platform not in LOGIN_FUNCS:
        print(f"不支持的平台: {platform}")
        return
    await LOGIN_FUNCS[platform]()


async def run_publish(platform: str):
    tasks = parse_video_dir(VIDEO_DIR)
    if not tasks:
        print(f"没有找到视频: {VIDEO_DIR}")
        return
    print(f"找到 {len(tasks)} 个视频，目标平台: {platform}")

    targets = PLATFORMS if platform == "all" else [platform]
    for p in targets:
        if p not in PUBLISH_FUNCS:
            print(f"不支持的平台: {p}")
            continue
        if platform == "all" and not COOKIES[p].exists():
            print(f"[{p}] 跳过（未登录）")
            continue
        func, is_async = PUBLISH_FUNCS[p]
        if is_async:
            await func(tasks)
        else:
            func(tasks)


async def main():
    if len(sys.argv) < 3:
        usage()
    action, platform = sys.argv[1], sys.argv[2]
    if action == "login":
        await run_login(platform)
    elif action == "run":
        await run_publish(platform)
    else:
        usage()


if __name__ == "__main__":
    asyncio.run(main())


path: scripts/setup.py
content: """
setup.py - 首次安装时运行，自动检测 Chrome 路径并写入 conf.py
用法：python setup.py
"""
import sys
import os
import subprocess
from pathlib import Path

CONF_PATH = Path(__file__).parent / "conf.py"

CHROME_CANDIDATES = [
    # Windows
    r"C:\Program Files\Google\Chrome\Application\chrome.exe",
    r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
    os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe"),
    # macOS
    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
    # Linux
    "/usr/bin/google-chrome",
    "/usr/bin/chromium-browser",
    "/usr/bin/chromium",
]


def find_chrome():
    for path in CHROME_CANDIDATES:
        if os.path.exists(path):
            return path
    return ""


def install_deps():
    print("安装依赖...")
    subprocess.check_call([sys.executable, "-m", "pip", "install",
                           "playwright", "biliup", "loguru", "requests"], stdout=subprocess.DEVNULL)
    subprocess.check_call([sys.executable, "-m", "playwright", "install", "chromium"])
    print("依赖安装完成")


def write_chrome_path(chrome_path: str):
    text = CONF_PATH.read_text(encoding="utf-8")
    text = text.replace(
        'LOCAL_CHROME_PATH = os.environ.get("CHROME_PATH", "")',
        f'LOCAL_CHROME_PATH = r"{chrome_path}"'
    )
    CONF_PATH.write_text(text, encoding="utf-8")


def main():
    print("=== video-publisher 安装向导 ===\n")

    # 1. 安装依赖
    install_deps()

    # 2. 检测 Chrome
    chrome = find_chrome()
    if chrome:
        print(f"检测到 Chrome: {chrome}")
        confirm = input("使用此路径？[Y/n] ").strip().lower()
        if confirm in ("", "y"):
            write_chrome_path(chrome)
        else:
            custom = input("请输入 Chrome 路径: ").strip()
            write_chrome_path(custom)
    else:
        custom = input("未检测到 Chrome，请手动输入路径: ").strip()
        write_chrome_path(custom)

    print("\n安装完成！")
    print("下一步：")
    print("  python publish.py login douyin    # 登录抖音")
    print("  python publish.py login bilibili  # 登录B站")
    print("  python publish.py run douyin      # 发布到抖音")
    print("  python publish.py run all         # 发布到所有平台")


if __name__ == "__main__":
    main()


path: scripts/utils/base_social_media.py
content: """
base_social_media.py - 自包含版，不依赖外部 conf
"""
from pathlib import Path
from typing import List

# BASE_DIR 指向 scripts/ 目录
BASE_DIR = Path(__file__).parent.parent

SOCIAL_MEDIA_DOUYIN = "douyin"
SOCIAL_MEDIA_TENCENT = "tencent"
SOCIAL_MEDIA_TIKTOK = "tiktok"
SOCIAL_MEDIA_BILIBILI = "bilibili"
SOCIAL_MEDIA_KUAISHOU = "kuaishou"


def get_supported_social_media() -> List[str]:
    return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU]


def get_cli_action() -> List[str]:
    return ["upload", "login", "watch"]


async def set_init_script(context):
    stealth_js_path = BASE_DIR / "utils" / "stealth.min.js"
    if stealth_js_path.exists():
        await context.add_init_script(path=str(stealth_js_path))
    return context


path: scripts/utils/constant.py
content: import enum


class TencentZoneTypes(enum.Enum):
    LIFESTYLE = '生活'
    CUTE_KIDS = '萌娃'
    MUSIC = '音乐'
    KNOWLEDGE = '知识'
    EMOTION = '情感'
    TRAVEL_SCENERY = '旅行风景'
    FASHION = '时尚'
    FOOD = '美食'
    LIFE_HACKS = '生活技巧'
    DANCE = '舞蹈'
    MOVIES_TV_SHOWS = '影视综艺'
    SPORTS = '运动'
    FUNNY = '搞笑'
    CELEBRITIES = '明星名人'
    NEWS_INFO = '新闻资讯'
    GAMING = '游戏'
    AUTOMOTIVE = '车'
    ANIME = '二次元'
    TALENT = '才艺'
    CUTE_PETS = '萌宠'
    INDUSTRY_MACHINERY_CONSTRUCTION = '机械'
    ANIMALS = '动物'
    PARENTING = '育儿'
    TECHNOLOGY = '科技'

class VideoZoneTypes(enum.Enum):
    """
    所有分区枚举

    - MAINPAGE: 主页
    - ANIME: 番剧
        - ANIME_SERIAL: 连载中番剧
        - ANIME_FINISH: 已完结番剧
        - ANIME_INFORMATION: 资讯
        - ANIME_OFFICAL: 官方延伸
    - MOVIE: 电影
    - GUOCHUANG: 国创
        - GUOCHUANG_CHINESE: 国产动画
        - GUOCHUANG_ORIGINAL: 国产原创相关
        - GUOCHUANG_PUPPETRY: 布袋戏
        - GUOCHUANG_MOTIONCOMIC: 动态漫·广播剧
        - GUOCHUANG_INFORMATION: 资讯
    - TELEPLAY: 电视剧
    - DOCUMENTARY: 纪录片
    - DOUGA: 动画
        - DOUGA_MAD: MAD·AMV
        - DOUGA_MMD: MMD·3D
        - DOUGA_VOICE: 短片·手书·配音
        - DOUGA_GARAGE_KIT: 手办·模玩
        - DOUGA_TOKUSATSU: 特摄
        - DOUGA_ACGNTALKS: 动漫杂谈
        - DOUGA_OTHER: 综合
    - GAME: 游戏
        - GAME_STAND_ALONE: 单机游戏
        - GAME_ESPORTS: 电子竞技
        - GAME_MOBILE: 手机游戏
        - GAME_ONLINE: 网络游戏
        - GAME_BOARD: 桌游棋牌
        - GAME_GMV: GMV
        - GAME_MUSIC: 音游
        - GAME_MUGEN: Mugen
    - KICHIKU: 鬼畜
        - KICHIKU_GUIDE: 鬼畜调教
        - KICHIKU_MAD: 音MAD
        - KICHIKU_MANUAL_VOCALOID: 人力VOCALOID
        - KICHIKU_THEATRE: 鬼畜剧场
        - KICHIKU_COURSE: 教程演示
    - MUSIC: 音乐
        - MUSIC_ORIGINAL: 原创音乐
        - MUSIC_COVER: 翻唱
        - MUSIC_PERFORM: 演奏
        - MUSIC_VOCALOID: VOCALOID·UTAU
        - MUSIC_LIVE: 音乐现场
        - MUSIC_MV: MV
        - MUSIC_COMMENTARY: 乐评盘点
        - MUSIC_TUTORIAL: 音乐教学
        - MUSIC_OTHER: 音乐综合
    - DANCE: 舞蹈
        - DANCE_OTAKU: 宅舞
        - DANCE_HIPHOP: 街舞
        - DANCE_STAR: 明星舞蹈
        - DANCE_CHINA: 中国舞
        - DANCE_THREE_D: 舞蹈综合
        - DANCE_DEMO: 舞蹈教程
    - CINEPHILE: 影视
        - CINEPHILE_CINECISM: 影视杂谈
        - CINEPHILE_MONTAGE: 影视剪辑
        - CINEPHILE_SHORTFILM: 小剧场
        - CINEPHILE_TRAILER_INFO: 预告·资讯
    - ENT: 娱乐
        - ENT_VARIETY: 综艺
        - ENT_TALKER: 娱乐杂谈
        - ENT_FANS: 粉丝创作
        - ENT_CELEBRITY: 明星综合
    - KNOWLEDGE: 知识
        - KNOWLEDGE_SCIENCE: 科学科普
        - KNOWLEDGE_SOCIAL_SCIENCE: 社科·法律·心理
        - KNOWLEDGE_HUMANITY_HISTORY: 人文历史
        - KNOWLEDGE_BUSINESS: 财经商业
        - KNOWLEDGE_CAMPUS: 校园学习
        - KNOWLEDGE_CAREER: 职业职场
        - KNOWLEDGE_DESIGN: 设计·创意
        - KNOWLEDGE_SKILL: 野生技能协会
    - TECH: 科技
        - TECH_DIGITAL: 数码
        - TECH_APPLICATION: 软件应用
        - TECH_COMPUTER_TECH: 计算机技术
        - TECH_INDUSTRY: 科工机械
    - INFORMATION: 资讯
        - INFORMATION_HOTSPOT: 热点
        - INFORMATION_GLOBAL: 环球
        - INFORMATION_SOCIAL: 社会
        - INFORMATION_MULTIPLE: 综合
    - FOOD: 美食
        - FOOD_MAKE: 美食制作
        - FOOD_DETECTIVE: 美食侦探
        - FOOD_MEASUREMENT: 美食测评
        - FOOD_RURAL: 田园美食
        - FOOD_RECORD: 美食记录
    - LIFE: 生活
        - LIFE_FUNNY: 搞笑
        - LIFE_TRAVEL: 出行
        - LIFE_RURALLIFE: 三农
        - LIFE_HOME: 家居房产
        - LIFE_HANDMAKE: 手工
        - LIFE_PAINTING: 绘画
        - LIFE_DAILY: 日常
    - CAR: 汽车
        - CAR_RACING: 赛车
        - CAR_MODIFIEDVEHICLE: 改装玩车
        - CAR_NEWENERGYVEHICLE: 新能源车
        - CAR_TOURINGCAR: 房车
        - CAR_MOTORCYCLE: 摩托车
        - CAR_STRATEGY: 购车攻略
        - CAR_LIFE: 汽车生活
    - FASHION: 时尚
        - FASHION_MAKEUP: 美妆护肤
        - FASHION_COS: 仿妆cos
        - FASHION_CLOTHING: 穿搭
        - FASHION_TREND: 时尚潮流
    - SPORTS: 运动
        - SPORTS_BASKETBALL: 篮球
        - SPORTS_FOOTBALL: 足球
        - SPORTS_AEROBICS: 健身
        - SPORTS_ATHLETIC: 竞技体育
        - SPORTS_CULTURE: 运动文化
        - SPORTS_COMPREHENSIVE: 运动综合
    - ANIMAL: 动物圈
        - ANIMAL_CAT: 喵星人
        - ANIMAL_DOG: 汪星人
        - ANIMAL_PANDA: 大熊猫
        - ANIMAL_WILD_ANIMAL: 野生动物
        - ANIMAL_REPTILES: 爬宠
        - ANIMAL_COMPOSITE: 动物综合
    - VLOG: VLOG
    """

    MAINPAGE = 0

    ANIME = 13
    ANIME_SERIAL = 33
    ANIME_FINISH = 32
    ANIME_INFORMATION = 51
    ANIME_OFFICAL = 152

    MOVIE = 23

    GUOCHUANG = 167
    GUOCHUANG_CHINESE = 153
    GUOCHUANG_ORIGINAL = 168
    GUOCHUANG_PUPPETRY = 169
    GUOCHUANG_MOTIONCOMIC = 195
    GUOCHUANG_INFORMATION = 170

    TELEPLAY = 11

    DOCUMENTARY = 177

    DOUGA = 1
    DOUGA_MAD = 24
    DOUGA_MMD = 25
    DOUGA_VOICE = 47
    DOUGA_GARAGE_KIT = 210
    DOUGA_TOKUSATSU = 86
    DOUGA_ACGNTALKS = 253
    DOUGA_OTHER = 27

    GAME = 4
    GAME_STAND_ALONE = 17
    GAME_ESPORTS = 171
    GAME_MOBILE = 172
    GAME_ONLINE = 65
    GAME_BOARD = 173
    GAME_GMV = 121
    GAME_MUSIC = 136
    GAME_MUGEN = 19

    KICHIKU = 119
    KICHIKU_GUIDE = 22
    KICHIKU_MAD = 26
    KICHIKU_MANUAL_VOCALOID = 126
    KICHIKU_THEATRE = 216
    KICHIKU_COURSE = 127

    MUSIC = 3
    MUSIC_ORIGINAL = 28
    MUSIC_COVER = 31
    MUSIC_PERFORM = 59
    MUSIC_VOCALOID = 30
    MUSIC_LIVE = 29
    MUSIC_MV = 193
    MUSIC_COMMENTARY = 243
    MUSIC_TUTORIAL = 244
    MUSIC_OTHER = 130

    DANCE = 129
    DANCE_OTAKU = 20
    DANCE_HIPHOP = 198
    DANCE_STAR = 199
    DANCE_CHINA = 200
    DANCE_THREE_D = 154
    DANCE_DEMO = 156

    CINEPHILE = 181
    CINEPHILE_CINECISM = 182
    CINEPHILE_MONTAGE = 183
    CINEPHILE_SHORTFILM = 85
    CINEPHILE_TRAILER_INFO = 184

    ENT = 5
    ENT_VARIETY = 71
    ENT_TALKER = 241
    ENT_FANS = 242
    ENT_CELEBRITY = 137

    KNOWLEDGE = 36
    KNOWLEDGE_SCIENCE = 201
    KNOWLEDGE_SOCIAL_SCIENCE = 124
    KNOWLEDGE_HUMANITY_HISTORY = 228
    KNOWLEDGE_BUSINESS = 207
    KNOWLEDGE_CAMPUS = 208
    KNOWLEDGE_CAREER = 209
    KNOWLEDGE_DESIGN = 229
    KNOWLEDGE_SKILL = 122

    TECH = 188
    TECH_DIGITAL = 95
    TECH_APPLICATION = 230
    TECH_COMPUTER_TECH = 231
    TECH_INDUSTRY = 232

    INFORMATION = 202
    INFORMATION_HOTSPOT = 203
    INFORMATION_GLOBAL = 204
    INFORMATION_SOCIAL = 205
    INFORMATION_MULTIPLE = 206

    FOOD = 211
    FOOD_MAKE = 76
    FOOD_DETECTIVE = 212
    FOOD_MEASUREMENT = 213
    FOOD_RURAL = 214
    FOOD_RECORD = 215

    LIFE = 160
    LIFE_FUNNY = 138
    LIFE_TRAVEL = 250
    LIFE_RURALLIFE = 251
    LIFE_HOME = 239
    LIFE_HANDMAKE = 161
    LIFE_PAINTING = 162
    LIFE_DAILY = 21

    CAR = 223
    CAR_RACING = 245
    CAR_MODIFIEDVEHICLE = 246
    CAR_NEWENERGYVEHICLE = 247
    CAR_TOURINGCAR = 248
    CAR_MOTORCYCLE = 240
    CAR_STRATEGY = 227
    CAR_LIFE = 176

    FASHION = 155
    FASHION_MAKEUP = 157
    FASHION_COS = 252
    FASHION_CLOTHING = 158
    FASHION_TREND = 159

    SPORTS = 234
    SPORTS_BASKETBALL = 235
    SPORTS_FOOTBALL = 249
    SPORTS_AEROBICS = 164
    SPORTS_ATHLETIC = 236
    SPORTS_CULTURE = 237
    SPORTS_COMPREHENSIVE = 238

    ANIMAL = 217
    ANIMAL_CAT = 218
    ANIMAL_DOG = 219
    ANIMAL_PANDA = 220
    ANIMAL_WILD_ANIMAL = 221
    ANIMAL_REPTILES = 222
    ANIMAL_COMPOSITE = 75

    VLOG = 19


path: scripts/utils/files_times.py
content: from datetime import timedelta

from datetime import datetime
from pathlib import Path

from conf import BASE_DIR


def get_absolute_path(relative_path: str, base_dir: str = None) -> str:
    # Convert the relative path to an absolute path
    absolute_path = Path(BASE_DIR) / base_dir / relative_path
    return str(absolute_path)


def get_title_and_hashtags(filename):
    """
  获取视频标题和 hashtag

  Args:
    filename: 视频文件名

  Returns:
    视频标题和 hashtag 列表
  """

    # 获取视频标题和 hashtag txt 文件名
    txt_filename = filename.replace(".mp4", ".txt")

    # 读取 txt 文件
    with open(txt_filename, "r", encoding="utf-8") as f:
        content = f.read()

    # 获取标题和 hashtag
    splite_str = content.strip().split("\n")
    title = splite_str[0]
    hashtags = splite_str[1].replace("#", "").split(" ")

    return title, hashtags


def generate_schedule_time_next_day(total_videos, videos_per_day = 1, daily_times=None, timestamps=False, start_days=0):
    """
    Generate a schedule for video uploads, starting from the next day.

    Args:
    - total_videos: Total number of videos to be uploaded.
    - videos_per_day: Number of videos to be uploaded each day.
    - daily_times: Optional list of specific times of the day to publish the videos.
    - timestamps: Boolean to decide whether to return timestamps or datetime objects.
    - start_days: Start from after start_days.

    Returns:
    - A list of scheduling times for the videos, either as timestamps or datetime objects.
    """
    if videos_per_day <= 0:
        raise ValueError("videos_per_day should be a positive integer")

    if daily_times is None:
        # Default times to publish videos if not provided
        daily_times = [6, 11, 14, 16, 22]

    if videos_per_day > len(daily_times):
        raise ValueError("videos_per_day should not exceed the length of daily_times")

    # Generate timestamps
    schedule = []
    current_time = datetime.now()

    for video in range(total_videos):
        day = video // videos_per_day + start_days + 1  # +1 to start from the next day
        daily_video_index = video % videos_per_day

        # Calculate the time for the current video
        hour = daily_times[daily_video_index]
        time_offset = timedelta(days=day, hours=hour - current_time.hour, minutes=-current_time.minute,
                                seconds=-current_time.second, microseconds=-current_time.microsecond)
        timestamp = current_time + time_offset

        schedule.append(timestamp)

    if timestamps:
        schedule = [int(time.timestamp()) for time in schedule]
    return schedule


path: scripts/utils/log.py
content: from pathlib import Path
from sys import stdout
from loguru import logger

from conf import BASE_DIR


def log_formatter(record: dict) -> str:
    """
    Formatter for log records.
    :param dict record: Log object containing log metadata & message.
    :returns: str
    """
    colors = {
        "TRACE": "#cfe2f3",
        "INFO": "#9cbfdd",
        "DEBUG": "#8598ea",
        "WARNING": "#dcad5a",
        "SUCCESS": "#3dd08d",
        "ERROR": "#ae2c2c"
    }
    color = colors.get(record["level"].name, "#b3cfe7")
    return f"<fg #70acde>{{time:YYYY-MM-DD HH:mm:ss}}</fg #70acde> | <fg {color}>{{level}}</fg {color}>: <light-white>{{message}}</light-white>\n"


def create_logger(log_name: str, file_path: str):
    """
    Create custom logger for different business modules.
    :param str log_name: name of log
    :param str file_path: Optional path to log file
    :returns: Configured logger
    """
    def filter_record(record):
        return record["extra"].get("business_name") == log_name

    Path(BASE_DIR / file_path).parent.mkdir(exist_ok=True)
    logger.add(Path(BASE_DIR / file_path), filter=filter_record, level="INFO", rotation="10 MB", retention="10 days", backtrace=True, diagnose=True)
    return logger.bind(business_name=log_name)


# Remove all existing handlers
logger.remove()
# Add a standard console handler
logger.add(stdout, colorize=True, format=log_formatter)

douyin_logger = create_logger('douyin', 'logs/douyin.log')
tencent_logger = create_logger('tencent', 'logs/tencent.log')
xhs_logger = create_logger('xhs', 'logs/xhs.log')
tiktok_logger = create_logger('tiktok', 'logs/tiktok.log')
bilibili_logger = create_logger('bilibili', 'logs/bilibili.log')
kuaishou_logger = create_logger('kuaishou', 'logs/kuaishou.log')
baijiahao_logger = create_logger('baijiahao', 'logs/baijiahao.log')
xiaohongshu_logger = create_logger('xiaohongshu', 'logs/xiaohongshu.log')


path: scripts/utils/stealth.min.js
content: /*!
 * Note: Auto-generated, do not update manually.
 * Generated by: https://github.com/berstend/puppeteer-extra/tree/master/packages/extract-stealth-evasions
 * Generated on: Mon, 10 Jun 2024 06:21:08 GMT
 * License: MIT
 */
(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:'utils => {\n      if (!window.chrome) {\n        // Use the exact property descriptor found in headful Chrome\n        // fetch it via `Object.getOwnPropertyDescriptor(window, \'chrome\')`\n        Object.defineProperty(window, \'chrome\', {\n          writable: true,\n          enumerable: true,\n          configurable: false, // note!\n          value: {} // We\'ll extend that later\n        })\n      }\n\n      // That means we\'re running headful and don\'t need to mock anything\n      if (\'app\' in window.chrome) {\n        return // Nothing to do here\n      }\n\n      const makeError = {\n        ErrorInInvocation: fn => {\n          const err = new TypeError(`Error in invocation of app.${fn}()`)\n          return utils.stripErrorWithAnchor(\n            err,\n            `at ${fn} (eval at <anonymous>`\n          )\n        }\n      }\n\n      // There\'s a some static data in that property which doesn\'t seem to change,\n      // we should periodically check for updates: `JSON.stringify(window.app, null, 2)`\n      const STATIC_DATA = JSON.parse(\n        `\n{\n  "isInstalled": false,\n  "InstallState": {\n    "DISABLED": "disabled",\n    "INSTALLED": "installed",\n    "NOT_INSTALLED": "not_installed"\n  },\n  "RunningState": {\n    "CANNOT_RUN": "cannot_run",\n    "READY_TO_RUN": "ready_to_run",\n    "RUNNING": "running"\n  }\n}\n        `.trim()\n      )\n\n      window.chrome.app = {\n        ...STATIC_DATA,\n\n        get isInstalled() {\n          return false\n        },\n\n        getDetails: function getDetails() {\n          if (arguments.length) {\n            throw makeError.ErrorInInvocation(`getDetails`)\n          }\n          return null\n        },\n        getIsInstalled: function getDetails() {\n          if (arguments.length) {\n            throw makeError.ErrorInInvocation(`getIsInstalled`)\n          }\n          return false\n        },\n        runningState: function getDetails() {\n          if (arguments.length) {\n            throw makeError.ErrorInInvocation(`runningState`)\n          }\n          return \'cannot_run\'\n        }\n      }\n      utils.patchToStringNested(window.chrome.app)\n    }',_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"utils => {\n      if (!window.chrome) {\n        // Use the exact property descriptor found in headful Chrome\n        // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n        Object.defineProperty(window, 'chrome', {\n          writable: true,\n          enumerable: true,\n          configurable: false, // note!\n          value: {} // We'll extend that later\n        })\n      }\n\n      // That means we're running headful and don't need to mock anything\n      if ('csi' in window.chrome) {\n        return // Nothing to do here\n      }\n\n      // Check that the Navigation Timing API v1 is available, we need that\n      if (!window.performance || !window.performance.timing) {\n        return\n      }\n\n      const { timing } = window.performance\n\n      window.chrome.csi = function() {\n        return {\n          onloadT: timing.domContentLoadedEventEnd,\n          startE: timing.navigationStart,\n          pageT: Date.now() - timing.navigationStart,\n          tran: 15 // Transition type or something\n        }\n      }\n      utils.patchToString(window.chrome.csi)\n    }",_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, { opts }) => {\n        if (!window.chrome) {\n          // Use the exact property descriptor found in headful Chrome\n          // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n          Object.defineProperty(window, 'chrome', {\n            writable: true,\n            enumerable: true,\n            configurable: false, // note!\n            value: {} // We'll extend that later\n          })\n        }\n\n        // That means we're running headful and don't need to mock anything\n        if ('loadTimes' in window.chrome) {\n          return // Nothing to do here\n        }\n\n        // Check that the Navigation Timing API v1 + v2 is available, we need that\n        if (\n          !window.performance ||\n          !window.performance.timing ||\n          !window.PerformancePaintTiming\n        ) {\n          return\n        }\n\n        const { performance } = window\n\n        // Some stuff is not available on about:blank as it requires a navigation to occur,\n        // let's harden the code to not fail then:\n        const ntEntryFallback = {\n          nextHopProtocol: 'h2',\n          type: 'other'\n        }\n\n        // The API exposes some funky info regarding the connection\n        const protocolInfo = {\n          get connectionInfo() {\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ntEntry.nextHopProtocol\n          },\n          get npnNegotiatedProtocol() {\n            // NPN is deprecated in favor of ALPN, but this implementation returns the\n            // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n              ? ntEntry.nextHopProtocol\n              : 'unknown'\n          },\n          get navigationType() {\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ntEntry.type\n          },\n          get wasAlternateProtocolAvailable() {\n            // The Alternate-Protocol header is deprecated in favor of Alt-Svc\n            // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this\n            // should always return false.\n            return false\n          },\n          get wasFetchedViaSpdy() {\n            // SPDY is deprecated in favor of HTTP/2, but this implementation returns\n            // true for HTTP/2 or HTTP2+QUIC/39 as well.\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n          },\n          get wasNpnNegotiated() {\n            // NPN is deprecated in favor of ALPN, but this implementation returns true\n            // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n          }\n        }\n\n        const { timing } = window.performance\n\n        // Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3\n        function toFixed(num, fixed) {\n          var re = new RegExp('^-?\\\\d+(?:.\\\\d{0,' + (fixed || -1) + '})?')\n          return num.toString().match(re)[0]\n        }\n\n        const timingInfo = {\n          get firstPaintAfterLoadTime() {\n            // This was never actually implemented and always returns 0.\n            return 0\n          },\n          get requestTime() {\n            return timing.navigationStart / 1000\n          },\n          get startLoadTime() {\n            return timing.navigationStart / 1000\n          },\n          get commitLoadTime() {\n            return timing.responseStart / 1000\n          },\n          get finishDocumentLoadTime() {\n            return timing.domContentLoadedEventEnd / 1000\n          },\n          get finishLoadTime() {\n            return timing.loadEventEnd / 1000\n          },\n          get firstPaintTime() {\n            const fpEntry = performance.getEntriesByType('paint')[0] || {\n              startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)\n            }\n            return toFixed(\n              (fpEntry.startTime + performance.timeOrigin) / 1000,\n              3\n            )\n          }\n        }\n\n        window.chrome.loadTimes = function() {\n          return {\n            ...protocolInfo,\n            ...timingInfo\n          }\n        }\n        utils.patchToString(window.chrome.loadTimes)\n      }",_args:[{opts:{}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, { opts, STATIC_DATA }) => {\n        if (!window.chrome) {\n          // Use the exact property descriptor found in headful Chrome\n          // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n          Object.defineProperty(window, 'chrome', {\n            writable: true,\n            enumerable: true,\n            configurable: false, // note!\n            value: {} // We'll extend that later\n          })\n        }\n\n        // That means we're running headful and don't need to mock anything\n        const existsAlready = 'runtime' in window.chrome\n        // `chrome.runtime` is only exposed on secure origins\n        const isNotSecure = !window.location.protocol.startsWith('https')\n        if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {\n          return // Nothing to do here\n        }\n\n        window.chrome.runtime = {\n          // There's a bunch of static data in that property which doesn't seem to change,\n          // we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)`\n          ...STATIC_DATA,\n          // `chrome.runtime.id` is extension related and returns undefined in Chrome\n          get id() {\n            return undefined\n          },\n          // These two require more sophisticated mocks\n          connect: null,\n          sendMessage: null\n        }\n\n        const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({\n          NoMatchingSignature: new TypeError(\n            preamble + `No matching signature.`\n          ),\n          MustSpecifyExtensionID: new TypeError(\n            preamble +\n              `${method} called from a webpage must specify an Extension ID (string) for its first argument.`\n          ),\n          InvalidExtensionID: new TypeError(\n            preamble + `Invalid extension id: '${extensionId}'`\n          )\n        })\n\n        // Valid Extension IDs are 32 characters in length and use the letter `a` to `p`:\n        // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90\n        const isValidExtensionID = str =>\n          str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)\n\n        /** Mock `chrome.runtime.sendMessage` */\n        const sendMessageHandler = {\n          apply: function(target, ctx, args) {\n            const [extensionId, options, responseCallback] = args || []\n\n            // Define custom errors\n            const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): `\n            const Errors = makeCustomRuntimeErrors(\n              errorPreamble,\n              `chrome.runtime.sendMessage()`,\n              extensionId\n            )\n\n            // Check if the call signature looks ok\n            const noArguments = args.length === 0\n            const tooManyArguments = args.length > 4\n            const incorrectOptions = options && typeof options !== 'object'\n            const incorrectResponseCallback =\n              responseCallback && typeof responseCallback !== 'function'\n            if (\n              noArguments ||\n              tooManyArguments ||\n              incorrectOptions ||\n              incorrectResponseCallback\n            ) {\n              throw Errors.NoMatchingSignature\n            }\n\n            // At least 2 arguments are required before we even validate the extension ID\n            if (args.length < 2) {\n              throw Errors.MustSpecifyExtensionID\n            }\n\n            // Now let's make sure we got a string as extension ID\n            if (typeof extensionId !== 'string') {\n              throw Errors.NoMatchingSignature\n            }\n\n            if (!isValidExtensionID(extensionId)) {\n              throw Errors.InvalidExtensionID\n            }\n\n            return undefined // Normal behavior\n          }\n        }\n        utils.mockWithProxy(\n          window.chrome.runtime,\n          'sendMessage',\n          function sendMessage() {},\n          sendMessageHandler\n        )\n\n        /**\n         * Mock `chrome.runtime.connect`\n         *\n         * @see https://developer.chrome.com/apps/runtime#method-connect\n         */\n        const connectHandler = {\n          apply: function(target, ctx, args) {\n            const [extensionId, connectInfo] = args || []\n\n            // Define custom errors\n            const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): `\n            const Errors = makeCustomRuntimeErrors(\n              errorPreamble,\n              `chrome.runtime.connect()`,\n              extensionId\n            )\n\n            // Behavior differs a bit from sendMessage:\n            const noArguments = args.length === 0\n            const emptyStringArgument = args.length === 1 && extensionId === ''\n            if (noArguments || emptyStringArgument) {\n              throw Errors.MustSpecifyExtensionID\n            }\n\n            const tooManyArguments = args.length > 2\n            const incorrectConnectInfoType =\n              connectInfo && typeof connectInfo !== 'object'\n\n            if (tooManyArguments || incorrectConnectInfoType) {\n              throw Errors.NoMatchingSignature\n            }\n\n            const extensionIdIsString = typeof extensionId === 'string'\n            if (extensionIdIsString && extensionId === '') {\n              throw Errors.MustSpecifyExtensionID\n            }\n            if (extensionIdIsString && !isValidExtensionID(extensionId)) {\n              throw Errors.InvalidExtensionID\n            }\n\n            // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate\n            const validateConnectInfo = ci => {\n              // More than a first param connectInfo as been provided\n              if (args.length > 1) {\n                throw Errors.NoMatchingSignature\n              }\n              // An empty connectInfo has been provided\n              if (Object.keys(ci).length === 0) {\n                throw Errors.MustSpecifyExtensionID\n              }\n              // Loop over all connectInfo props an check them\n              Object.entries(ci).forEach(([k, v]) => {\n                const isExpected = ['name', 'includeTlsChannelId'].includes(k)\n                if (!isExpected) {\n                  throw new TypeError(\n                    errorPreamble + `Unexpected property: '${k}'.`\n                  )\n                }\n                const MismatchError = (propName, expected, found) =>\n                  TypeError(\n                    errorPreamble +\n                      `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.`\n                  )\n                if (k === 'name' && typeof v !== 'string') {\n                  throw MismatchError(k, 'string', typeof v)\n                }\n                if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {\n                  throw MismatchError(k, 'boolean', typeof v)\n                }\n              })\n            }\n            if (typeof extensionId === 'object') {\n              validateConnectInfo(extensionId)\n              throw Errors.MustSpecifyExtensionID\n            }\n\n            // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well\n            return utils.patchToStringNested(makeConnectResponse())\n          }\n        }\n        utils.mockWithProxy(\n          window.chrome.runtime,\n          'connect',\n          function connect() {},\n          connectHandler\n        )\n\n        function makeConnectResponse() {\n          const onSomething = () => ({\n            addListener: function addListener() {},\n            dispatch: function dispatch() {},\n            hasListener: function hasListener() {},\n            hasListeners: function hasListeners() {\n              return false\n            },\n            removeListener: function removeListener() {}\n          })\n\n          const response = {\n            name: '',\n            sender: undefined,\n            disconnect: function disconnect() {},\n            onDisconnect: onSomething(),\n            onMessage: onSomething(),\n            postMessage: function postMessage() {\n              if (!arguments.length) {\n                throw new TypeError(`Insufficient number of arguments.`)\n              }\n              throw new Error(`Attempting to use a disconnected port object`)\n            }\n          }\n          return response\n        }\n      }",_args:[{opts:{runOnInsecureOrigins:!1},STATIC_DATA:{OnInstalledReason:{CHROME_UPDATE:"chrome_update",INSTALL:"install",SHARED_MODULE_UPDATE:"shared_module_update",UPDATE:"update"},OnRestartRequiredReason:{APP_UPDATE:"app_update",OS_UPDATE:"os_update",PERIODIC:"periodic"},PlatformArch:{ARM:"arm",ARM64:"arm64",MIPS:"mips",MIPS64:"mips64",X86_32:"x86-32",X86_64:"x86-64"},PlatformNaclArch:{ARM:"arm",MIPS:"mips",MIPS64:"mips64",X86_32:"x86-32",X86_64:"x86-64"},PlatformOs:{ANDROID:"android",CROS:"cros",LINUX:"linux",MAC:"mac",OPENBSD:"openbsd",WIN:"win"},RequestUpdateCheckStatus:{NO_UPDATE:"no_update",THROTTLED:"throttled",UPDATE_AVAILABLE:"update_available"}}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"utils => {\n      /**\n       * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.\n       *\n       * @example\n       * video/webm; codecs=\"vp8, vorbis\"\n       * video/mp4; codecs=\"avc1.42E01E\"\n       * audio/x-m4a;\n       * audio/ogg; codecs=\"vorbis\"\n       * @param {String} arg\n       */\n      const parseInput = arg => {\n        const [mime, codecStr] = arg.trim().split(';')\n        let codecs = []\n        if (codecStr && codecStr.includes('codecs=\"')) {\n          codecs = codecStr\n            .trim()\n            .replace(`codecs=\"`, '')\n            .replace(`\"`, '')\n            .trim()\n            .split(',')\n            .filter(x => !!x)\n            .map(x => x.trim())\n        }\n        return {\n          mime,\n          codecStr,\n          codecs\n        }\n      }\n\n      const canPlayType = {\n        // Intercept certain requests\n        apply: function(target, ctx, args) {\n          if (!args || !args.length) {\n            return target.apply(ctx, args)\n          }\n          const { mime, codecs } = parseInput(args[0])\n          // This specific mp4 codec is missing in Chromium\n          if (mime === 'video/mp4') {\n            if (codecs.includes('avc1.42E01E')) {\n              return 'probably'\n            }\n          }\n          // This mimetype is only supported if no codecs are specified\n          if (mime === 'audio/x-m4a' && !codecs.length) {\n            return 'maybe'\n          }\n\n          // This mimetype is only supported if no codecs are specified\n          if (mime === 'audio/aac' && !codecs.length) {\n            return 'probably'\n          }\n          // Everything else as usual\n          return target.apply(ctx, args)\n        }\n      }\n\n      /* global HTMLMediaElement */\n      utils.replaceWithProxy(\n        HTMLMediaElement.prototype,\n        'canPlayType',\n        canPlayType\n      )\n    }",_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, { opts }) => {\n        utils.replaceGetterWithProxy(\n          Object.getPrototypeOf(navigator),\n          'hardwareConcurrency',\n          utils.makeHandler().getterValue(opts.hardwareConcurrency)\n        )\n      }",_args:[{opts:{hardwareConcurrency:4}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, { opts }) => {\n        const languages = opts.languages.length\n          ? opts.languages\n          : ['en-US', 'en']\n        utils.replaceGetterWithProxy(\n          Object.getPrototypeOf(navigator),\n          'languages',\n          utils.makeHandler().getterValue(Object.freeze([...languages]))\n        )\n      }",_args:[{opts:{languages:[]}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, opts) => {\n      const isSecure = document.location.protocol.startsWith('https')\n\n      // In headful on secure origins the permission should be \"default\", not \"denied\"\n      if (isSecure) {\n        utils.replaceGetterWithProxy(Notification, 'permission', {\n          apply() {\n            return 'default'\n          }\n        })\n      }\n\n      // Another weird behavior:\n      // On insecure origins in headful the state is \"denied\",\n      // whereas in headless it's \"prompt\"\n      if (!isSecure) {\n        const handler = {\n          apply(target, ctx, args) {\n            const param = (args || [])[0]\n\n            const isNotifications =\n              param && param.name && param.name === 'notifications'\n            if (!isNotifications) {\n              return utils.cache.Reflect.apply(...arguments)\n            }\n\n            return Promise.resolve(\n              Object.setPrototypeOf(\n                {\n                  state: 'denied',\n                  onchange: null\n                },\n                PermissionStatus.prototype\n              )\n            )\n          }\n        }\n        // Note: Don't use `Object.getPrototypeOf` here\n        utils.replaceWithProxy(Permissions.prototype, 'query', handler)\n      }\n    }",_args:[{}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, { fns, data }) => {\n        fns = utils.materializeFns(fns)\n\n        // That means we're running headful\n        const hasPlugins = 'plugins' in navigator && navigator.plugins.length\n        if (hasPlugins) {\n          return // nothing to do here\n        }\n\n        const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)\n        const plugins = fns.generatePluginArray(utils, fns)(data.plugins)\n\n        // Plugin and MimeType cross-reference each other, let's do that now\n        // Note: We're looping through `data.plugins` here, not the generated `plugins`\n        for (const pluginData of data.plugins) {\n          pluginData.__mimeTypes.forEach((type, index) => {\n            plugins[pluginData.name][index] = mimeTypes[type]\n\n            Object.defineProperty(plugins[pluginData.name], type, {\n              value: mimeTypes[type],\n              writable: false,\n              enumerable: false, // Not enumerable\n              configurable: true\n            })\n            Object.defineProperty(mimeTypes[type], 'enabledPlugin', {\n              value:\n                type === 'application/x-pnacl'\n                  ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks\n                  : new Proxy(plugins[pluginData.name], {}), // Prevent circular references\n              writable: false,\n              enumerable: false, // Important: `JSON.stringify(navigator.plugins)`\n              configurable: true\n            })\n          })\n        }\n\n        const patchNavigator = (name, value) =>\n          utils.replaceProperty(Object.getPrototypeOf(navigator), name, {\n            get() {\n              return value\n            }\n          })\n\n        patchNavigator('mimeTypes', mimeTypes)\n        patchNavigator('plugins', plugins)\n\n        // All done\n      }",_args:[{fns:{generateMimeTypeArray:"(utils, fns) => mimeTypesData => {\n  return fns.generateMagicArray(utils, fns)(\n    mimeTypesData,\n    MimeTypeArray.prototype,\n    MimeType.prototype,\n    'type'\n  )\n}",generatePluginArray:"(utils, fns) => pluginsData => {\n  return fns.generateMagicArray(utils, fns)(\n    pluginsData,\n    PluginArray.prototype,\n    Plugin.prototype,\n    'name'\n  )\n}",generateMagicArray:"(utils, fns) =>\n  function(\n    dataArray = [],\n    proto = MimeTypeArray.prototype,\n    itemProto = MimeType.prototype,\n    itemMainProp = 'type'\n  ) {\n    // Quick helper to set props with the same descriptors vanilla is using\n    const defineProp = (obj, prop, value) =>\n      Object.defineProperty(obj, prop, {\n        value,\n        writable: false,\n        enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`\n        configurable: true\n      })\n\n    // Loop over our fake data and construct items\n    const makeItem = data => {\n      const item = {}\n      for (const prop of Object.keys(data)) {\n        if (prop.startsWith('__')) {\n          continue\n        }\n        defineProp(item, prop, data[prop])\n      }\n      return patchItem(item, data)\n    }\n\n    const patchItem = (item, data) => {\n      let descriptor = Object.getOwnPropertyDescriptors(item)\n\n      // Special case: Plugins have a magic length property which is not enumerable\n      // e.g. `navigator.plugins[i].length` should always be the length of the assigned mimeTypes\n      if (itemProto === Plugin.prototype) {\n        descriptor = {\n          ...descriptor,\n          length: {\n            value: data.__mimeTypes.length,\n            writable: false,\n            enumerable: false,\n            configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`\n          }\n        }\n      }\n\n      // We need to spoof a specific `MimeType` or `Plugin` object\n      const obj = Object.create(itemProto, descriptor)\n\n      // Virtually all property keys are not enumerable in vanilla\n      const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']\n      return new Proxy(obj, {\n        ownKeys(target) {\n          return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))\n        },\n        getOwnPropertyDescriptor(target, prop) {\n          if (blacklist.includes(prop)) {\n            return undefined\n          }\n          return Reflect.getOwnPropertyDescriptor(target, prop)\n        }\n      })\n    }\n\n    const magicArray = []\n\n    // Loop through our fake data and use that to create convincing entities\n    dataArray.forEach(data => {\n      magicArray.push(makeItem(data))\n    })\n\n    // Add direct property access  based on types (e.g. `obj['application/pdf']`) afterwards\n    magicArray.forEach(entry => {\n      defineProp(magicArray, entry[itemMainProp], entry)\n    })\n\n    // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`\n    const magicArrayObj = Object.create(proto, {\n      ...Object.getOwnPropertyDescriptors(magicArray),\n\n      // There's one ugly quirk we unfortunately need to take care of:\n      // The `MimeTypeArray` prototype has an enumerable `length` property,\n      // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.\n      // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.\n      length: {\n        value: magicArray.length,\n        writable: false,\n        enumerable: false,\n        configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`\n      }\n    })\n\n    // Generate our functional function mocks :-)\n    const functionMocks = fns.generateFunctionMocks(utils)(\n      proto,\n      itemMainProp,\n      magicArray\n    )\n\n    // We need to overlay our custom object with a JS Proxy\n    const magicArrayObjProxy = new Proxy(magicArrayObj, {\n      get(target, key = '') {\n        // Redirect function calls to our custom proxied versions mocking the vanilla behavior\n        if (key === 'item') {\n          return functionMocks.item\n        }\n        if (key === 'namedItem') {\n          return functionMocks.namedItem\n        }\n        if (proto === PluginArray.prototype && key === 'refresh') {\n          return functionMocks.refresh\n        }\n        // Everything else can pass through as normal\n        return utils.cache.Reflect.get(...arguments)\n      },\n      ownKeys(target) {\n        // There are a couple of quirks where the original property demonstrates \"magical\" behavior that makes no sense\n        // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`\n        // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly\n        // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing\n        // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing\n        const keys = []\n        const typeProps = magicArray.map(mt => mt[itemMainProp])\n        typeProps.forEach((_, i) => keys.push(`${i}`))\n        typeProps.forEach(propName => keys.push(propName))\n        return keys\n      },\n      getOwnPropertyDescriptor(target, prop) {\n        if (prop === 'length') {\n          return undefined\n        }\n        return Reflect.getOwnPropertyDescriptor(target, prop)\n      }\n    })\n\n    return magicArrayObjProxy\n  }",generateFunctionMocks:"utils => (\n  proto,\n  itemMainProp,\n  dataArray\n) => ({\n  /** Returns the MimeType object with the specified index. */\n  item: utils.createProxy(proto.item, {\n    apply(target, ctx, args) {\n      if (!args.length) {\n        throw new TypeError(\n          `Failed to execute 'item' on '${\n            proto[Symbol.toStringTag]\n          }': 1 argument required, but only 0 present.`\n        )\n      }\n      // Special behavior alert:\n      // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup\n      // - If anything else than an integer (including as string) is provided it will return the first entry\n      const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer\n      // Note: Vanilla never returns `undefined`\n      return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null\n    }\n  }),\n  /** Returns the MimeType object with the specified name. */\n  namedItem: utils.createProxy(proto.namedItem, {\n    apply(target, ctx, args) {\n      if (!args.length) {\n        throw new TypeError(\n          `Failed to execute 'namedItem' on '${\n            proto[Symbol.toStringTag]\n          }': 1 argument required, but only 0 present.`\n        )\n      }\n      return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`!\n    }\n  }),\n  /** Does nothing and shall return nothing */\n  refresh: proto.refresh\n    ? utils.createProxy(proto.refresh, {\n        apply(target, ctx, args) {\n          return undefined\n        }\n      })\n    : undefined\n})"},data:{mimeTypes:[{type:"application/pdf",suffixes:"pdf",description:"",__pluginName:"Chrome PDF Viewer"},{type:"application/x-google-chrome-pdf",suffixes:"pdf",description:"Portable Document Format",__pluginName:"Chrome PDF Plugin"},{type:"application/x-nacl",suffixes:"",description:"Native Client Executable",__pluginName:"Native Client"},{type:"application/x-pnacl",suffixes:"",description:"Portable Native Client Executable",__pluginName:"Native Client"}],plugins:[{name:"Chrome PDF Plugin",filename:"internal-pdf-viewer",description:"Portable Document Format",__mimeTypes:["application/x-google-chrome-pdf"]},{name:"Chrome PDF Viewer",filename:[REDACTED_SECRET],description:"",__mimeTypes:["application/pdf"]},{name:"Native Client",filename:"internal-nacl-plugin",description:"",__mimeTypes:["application/x-nacl","application/x-pnacl"]}]}}]}),!1===navigator.webdriver||void 0===navigator.webdriver||delete Object.getPrototypeOf(navigator).webdriver,(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, opts) => {\n      const getParameterProxyHandler = {\n        apply: function(target, ctx, args) {\n          const param = (args || [])[0]\n          const result = utils.cache.Reflect.apply(target, ctx, args)\n          // UNMASKED_VENDOR_WEBGL\n          if (param === 37445) {\n            return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.\n          }\n          // UNMASKED_RENDERER_WEBGL\n          if (param === 37446) {\n            return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader\n          }\n          return result\n        }\n      }\n\n      // There's more than one WebGL rendering context\n      // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility\n      // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)\n      const addProxy = (obj, propName) => {\n        utils.replaceWithProxy(obj, propName, getParameterProxyHandler)\n      }\n      // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:\n      addProxy(WebGLRenderingContext.prototype, 'getParameter')\n      addProxy(WebGL2RenderingContext.prototype, 'getParameter')\n    }",_args:[{}]}),(()=>{try{if(window.outerWidth&&window.outerHeight)return;const n=85;window.outerWidth=window.innerWidth,window.outerHeight=window.innerHeight+n}catch(n){}})(),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, opts) => {\n      try {\n        // Adds a contentWindow proxy to the provided iframe element\n        const addContentWindowProxy = iframe => {\n          const contentWindowProxy = {\n            get(target, key) {\n              // Now to the interesting part:\n              // We actually make this thing behave like a regular iframe window,\n              // by intercepting calls to e.g. `.self` and redirect it to the correct thing. :)\n              // That makes it possible for these assertions to be correct:\n              // iframe.contentWindow.self === window.top // must be false\n              if (key === 'self') {\n                return this\n              }\n              // iframe.contentWindow.frameElement === iframe // must be true\n              if (key === 'frameElement') {\n                return iframe\n              }\n              // Intercept iframe.contentWindow[0] to hide the property 0 added by the proxy.\n              if (key === '0') {\n                return undefined\n              }\n              return Reflect.get(target, key)\n            }\n          }\n\n          if (!iframe.contentWindow) {\n            const proxy = new Proxy(window, contentWindowProxy)\n            Object.defineProperty(iframe, 'contentWindow', {\n              get() {\n                return proxy\n              },\n              set(newValue) {\n                return newValue // contentWindow is immutable\n              },\n              enumerable: true,\n              configurable: false\n            })\n          }\n        }\n\n        // Handles iframe element creation, augments `srcdoc` property so we can intercept further\n        const handleIframeCreation = (target, thisArg, args) => {\n          const iframe = target.apply(thisArg, args)\n\n          // We need to keep the originals around\n          const _iframe = iframe\n          const _srcdoc = _iframe.srcdoc\n\n          // Add hook for the srcdoc property\n          // We need to be very surgical here to not break other iframes by accident\n          Object.defineProperty(iframe, 'srcdoc', {\n            configurable: true, // Important, so we can reset this later\n            get: function() {\n              return _srcdoc\n            },\n            set: function(newValue) {\n              addContentWindowProxy(this)\n              // Reset property, the hook is only needed once\n              Object.defineProperty(iframe, 'srcdoc', {\n                configurable: false,\n                writable: false,\n                value: _srcdoc\n              })\n              _iframe.srcdoc = newValue\n            }\n          })\n          return iframe\n        }\n\n        // Adds a hook to intercept iframe creation events\n        const addIframeCreationSniffer = () => {\n          /* global document */\n          const createElementHandler = {\n            // Make toString() native\n            get(target, key) {\n              return Reflect.get(target, key)\n            },\n            apply: function(target, thisArg, args) {\n              const isIframe =\n                args && args.length && `${args[0]}`.toLowerCase() === 'iframe'\n              if (!isIframe) {\n                // Everything as usual\n                return target.apply(thisArg, args)\n              } else {\n                return handleIframeCreation(target, thisArg, args)\n              }\n            }\n          }\n          // All this just due to iframes with srcdoc bug\n          utils.replaceWithProxy(\n            document,\n            'createElement',\n            createElementHandler\n          )\n        }\n\n        // Let's go\n        addIframeCreationSniffer()\n      } catch (err) {\n        // console.warn(err)\n      }\n    }",_args:[]});

path: scripts/utils/__init__.py
content: 


path: scripts/uploader/__init__.py
content: 


path: scripts/uploader/xhs_uploader/main.py
content: import configparser
import json
import pathlib
from time import sleep

import requests
from playwright.sync_api import sync_playwright

from conf import BASE_DIR, XHS_SERVER, LOCAL_CHROME_HEADLESS

config = configparser.RawConfigParser()
config.read('accounts.ini')


def sign_local(uri, data=None, a1="", web_session=""):
    for _ in range(10):
        try:
            with sync_playwright() as playwright:
                stealth_js_path = pathlib.Path(BASE_DIR / "utils/stealth.min.js")
                chromium = playwright.chromium

                # 如果一直失败可尝试设置成 False 让其打开浏览器，适当添加 sleep 可查看浏览器状态
                browser = chromium.launch(headless=LOCAL_CHROME_HEADLESS)

                browser_context = browser.new_context()
                browser_context.add_init_script(path=stealth_js_path)
                context_page = browser_context.new_page()
                context_page.goto("https://www.xiaohongshu.com")
                browser_context.add_cookies([
                    {'name': 'a1', 'value': a1, 'domain': ".xiaohongshu.com", 'path': "/"}]
                )
                context_page.reload()
                # 这个地方设置完浏览器 cookie 之后，如果这儿不 sleep 一下签名获取就失败了，如果经常失败请设置长一点试试
                sleep(2)
                encrypt_params = context_page.evaluate("([url, data]) => window._webmsxyw(url, data)", [uri, data])
                return {
                    "x-s": encrypt_params["X-s"],
                    "x-t": str(encrypt_params["X-t"])
                }
        except Exception:
            # 这儿有时会出现 window._webmsxyw is not a function 或未知跳转错误，因此加一个失败重试趴
            pass
    raise Exception("重试了这么多次还是无法签名成功，寄寄寄")


def sign(uri, data=None, a1="", web_session=""):
    # 填写自己的 flask 签名服务端口地址
    res = requests.post(f"{XHS_SERVER}/sign",
                        json={"uri": uri, "data": data, "a1": a1, "web_session": web_session})
    signs = res.json()
    return {
        "x-s": signs["x-s"],
        "x-t": signs["x-t"]
    }


def beauty_print(data: dict):
    print(json.dumps(data, ensure_ascii=False, indent=2))


path: scripts/uploader/xhs_uploader/__init__.py
content: 


path: scripts/uploader/tencent_uploader/main.py
content: # -*- coding: utf-8 -*-
from datetime import datetime

from playwright.async_api import Playwright, async_playwright
import os
import asyncio

from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS
from utils.base_social_media import set_init_script
from utils.files_times import get_absolute_path
from utils.log import tencent_logger


def format_str_for_short_title(origin_title: str) -> str:
    # 定义允许的特殊字符
    allowed_special_chars = "《》“”:+?%°"

    # 移除不允许的特殊字符
    filtered_chars = [char if char.isalnum() or char in allowed_special_chars else ' ' if char == ',' else '' for
                      char in origin_title]
    formatted_string = ''.join(filtered_chars)

    # 调整字符串长度
    if len(formatted_string) > 16:
        # 截断字符串
        formatted_string = formatted_string[:16]
    elif len(formatted_string) < 6:
        # 使用空格来填充字符串
        formatted_string += ' ' * (6 - len(formatted_string))

    return formatted_string


async def cookie_auth(account_file):
    async with async_playwright() as playwright:
        browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS)
        context = await browser.new_context(storage_state=account_file)
        context = await set_init_script(context)
        # 创建一个新的页面
        page = await context.new_page()
        # 访问指定的 URL
        await page.goto("https://channels.weixin.qq.com/platform/post/create")
        try:
            await page.wait_for_selector('div.title-name:has-text("微信小店")', timeout=5000)  # 等待5秒
            tencent_logger.error("[+] 等待5秒 cookie 失效")
            return False
        except:
            tencent_logger.success("[+] cookie 有效")
            return True


async def get_tencent_cookie(account_file):
    async with async_playwright() as playwright:
        options = {
            'args': [
                '--lang en-GB'
            ],
            'headless': LOCAL_CHROME_HEADLESS,  # Set headless option here
        }
        # Make sure to run headed.
        browser = await playwright.chromium.launch(**options)
        # Setup context however you like.
        context = await browser.new_context()  # Pass any options
        # Pause the page, and start recording manually.
        context = await set_init_script(context)
        page = await context.new_page()
        await page.goto("https://channels.weixin.qq.com")
        await page.pause()
        # 点击调试器的继续，保存cookie
        await context.storage_state(path=account_file)


async def weixin_setup(account_file, handle=False):
    account_file = get_absolute_path(account_file, "tencent_uploader")
    if not os.path.exists(account_file) or not await cookie_auth(account_file):
        if not handle:
            # Todo alert message
            return False
        tencent_logger.info('[+] cookie文件不存在或已失效，即将自动打开浏览器，请扫码登录，登陆后会自动生成cookie文件')
        await get_tencent_cookie(account_file)
    return True


class TencentVideo(object):
    def __init__(self, title, file_path, tags, publish_date: datetime, account_file, category=None, is_draft=False):
        self.title = title  # 视频标题
        self.file_path = file_path
        self.tags = tags
        self.publish_date = publish_date
        self.account_file = account_file
        self.category = category
        self.headless = LOCAL_CHROME_HEADLESS
        self.is_draft = is_draft  # 是否保存为草稿
        self.local_executable_path = LOCAL_CHROME_PATH or None

    async def set_schedule_time_tencent(self, page, publish_date):
        label_element = page.locator("label").filter(has_text="定时").nth(1)
        await label_element.click()

        await page.click('input[placeholder="请选择发表时间"]')

        str_month = str(publish_date.month) if publish_date.month > 9 else "0" + str(publish_date.month)
        current_month = str_month + "月"
        # 获取当前的月份
        page_month = await page.inner_text('span.weui-desktop-picker__panel__label:has-text("月")')

        # 检查当前月份是否与目标月份相同
        if page_month != current_month:
            await page.click('button.weui-desktop-btn__icon__right')

        # 获取页面元素
        elements = await page.query_selector_all('table.weui-desktop-picker__table a')

        # 遍历元素并点击匹配的元素
        for element in elements:
            if 'weui-desktop-picker__disabled' in await element.evaluate('el => el.className'):
                continue
            text = await element.inner_text()
            if text.strip() == str(publish_date.day):
                await element.click()
                break

        # 输入小时部分（假设选择11小时）
        await page.click('input[placeholder="请选择时间"]')
        await page.keyboard.press("Control+KeyA")
        await page.keyboard.type(str(publish_date.hour))

        # 选择标题栏（令定时时间生效）
        await page.locator("div.input-editor").click()

    async def handle_upload_error(self, page):
        tencent_logger.info("视频出错了，重新上传中")
        await page.locator('div.media-status-content div.tag-inner:has-text("删除")').click()
        await page.get_by_role('button', name="删除", exact=True).click()
        file_input = page.locator('input[type="file"]')
        await file_input.set_input_files(self.file_path)

    async def upload(self, playwright: Playwright) -> None:
        # 使用 Chromium (这里使用系统内浏览器，用chromium 会造成h264错误
        browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path)
        # 创建一个浏览器上下文，使用指定的 cookie 文件
        context = await browser.new_context(storage_state=f"{self.account_file}")
        context = await set_init_script(context)

        # 创建一个新的页面
        page = await context.new_page()
        # 访问指定的 URL
        await page.goto("https://channels.weixin.qq.com/platform/post/create")
        tencent_logger.info(f'[+]正在上传-------{self.title}.mp4')
        # 等待页面跳转到指定的 URL，没进入，则自动等待到超时
        await page.wait_for_url("https://channels.weixin.qq.com/platform/post/create")
        # await page.wait_for_selector('input[type="file"]', timeout=10000)
        file_input = page.locator('input[type="file"]')
        await file_input.set_input_files(self.file_path)
        # 填充标题和话题
        await self.add_title_tags(page)
        # 添加商品
        # await self.add_product(page)
        # 合集功能
        await self.add_collection(page)
        # 原创选择
        await self.add_original(page)
        # 检测上传状态
        await self.detect_upload_status(page)
        if self.publish_date != 0:
            await self.set_schedule_time_tencent(page, self.publish_date)
        # 添加短标题
        await self.add_short_title(page)

        await self.click_publish(page)

        await context.storage_state(path=f"{self.account_file}")  # 保存cookie
        tencent_logger.success('  [-]cookie更新完毕！')
        await asyncio.sleep(2)  # 这里延迟是为了方便眼睛直观的观看
        # 关闭浏览器上下文和浏览器实例
        await context.close()
        await browser.close()

    async def add_short_title(self, page):
        short_title_element = page.get_by_text("短标题", exact=True).locator("..").locator(
            "xpath=following-sibling::div").locator(
            'span input[type="text"]')
        if await short_title_element.count():
            short_title = format_str_for_short_title(self.title)
            await short_title_element.fill(short_title)

    async def click_publish(self, page):
        while True:
            try:
                if self.is_draft:
                    # 点击"保存草稿"按钮
                    draft_button = page.locator('div.form-btns button:has-text("保存草稿")')
                    if await draft_button.count():
                        await draft_button.click()
                    # 等待跳转到草稿箱页面或确认保存成功
                    await page.wait_for_url("**/post/list**", timeout=5000)  # 使用通配符匹配包含post/list的URL
                    tencent_logger.success("  [-]视频草稿保存成功")
                else:
                    # 点击"发表"按钮
                    publish_button = page.locator('div.form-btns button:has-text("发表")')
                    if await publish_button.count():
                        await publish_button.click()
                    await page.wait_for_url("https://channels.weixin.qq.com/platform/post/list", timeout=5000)
                    tencent_logger.success("  [-]视频发布成功")
                break
            except Exception as e:
                current_url = page.url
                if self.is_draft:
                    # 检查是否在草稿相关的页面
                    if "post/list" in current_url or "draft" in current_url:
                        tencent_logger.success("  [-]视频草稿保存成功")
                        break
                else:
                    # 检查是否在发布列表页面
                    if "https://channels.weixin.qq.com/platform/post/list" in current_url:
                        tencent_logger.success("  [-]视频发布成功")
                        break
                tencent_logger.exception(f"  [-] Exception: {e}")
                tencent_logger.info("  [-] 视频正在发布中...")
                await asyncio.sleep(0.5)

    async def detect_upload_status(self, page):
        while True:
            # 匹配删除按钮，代表视频上传完毕，如果不存在，代表视频正在上传，则等待
            try:
                # 匹配删除按钮，代表视频上传完毕
                if "weui-desktop-btn_disabled" not in await page.get_by_role("button", name="发表").get_attribute(
                        'class'):
                    tencent_logger.info("  [-]视频上传完毕")
                    break
                else:
                    tencent_logger.info("  [-] 正在上传视频中...")
                    await asyncio.sleep(2)
                    # 出错了视频出错
                    if await page.locator('div.status-msg.error').count() and await page.locator(
                            'div.media-status-content div.tag-inner:has-text("删除")').count():
                        tencent_logger.error("  [-] 发现上传出错了...准备重试")
                        await self.handle_upload_error(page)
            except:
                tencent_logger.info("  [-] 正在上传视频中...")
                await asyncio.sleep(2)

    async def add_title_tags(self, page):
        await page.locator("div.input-editor").click()
        await page.keyboard.type(self.title)
        await page.keyboard.press("Enter")
        for index, tag in enumerate(self.tags, start=1):
            await page.keyboard.type("#" + tag)
            await page.keyboard.press("Space")
        tencent_logger.info(f"成功添加hashtag: {len(self.tags)}")

    async def add_collection(self, page):
        collection_elements = page.get_by_text("添加到合集").locator("xpath=following-sibling::div").locator(
            '.option-list-wrap > div')
        if await collection_elements.count() > 1:
            await page.get_by_text("添加到合集").locator("xpath=following-sibling::div").click()
            await collection_elements.first.click()

    async def add_original(self, page):
        if await page.get_by_label("视频为原创").count():
            await page.get_by_label("视频为原创").check()
        # 检查 "我已阅读并同意 《视频号原创声明使用条款》" 元素是否存在
        label_locator = await page.locator('label:has-text("我已阅读并同意 《视频号原创声明使用条款》")').is_visible()
        if label_locator:
            await page.get_by_label("我已阅读并同意 《视频号原创声明使用条款》").check()
            await page.get_by_role("button", name="声明原创").click()
        # 2023年11月20日 wechat更新: 可能新账号或者改版账号，出现新的选择页面
        if await page.locator('div.label span:has-text("声明原创")').count() and self.category:
            # 因处罚无法勾选原创，故先判断是否可用
            if not await page.locator('div.declare-original-checkbox input.ant-checkbox-input').is_disabled():
                await page.locator('div.declare-original-checkbox input.ant-checkbox-input').click()
                if not await page.locator(
                        'div.declare-original-dialog label.ant-checkbox-wrapper.ant-checkbox-wrapper-checked:visible').count():
                    await page.locator('div.declare-original-dialog input.ant-checkbox-input:visible').click()
            if await page.locator('div.original-type-form > div.form-label:has-text("原创类型"):visible').count():
                await page.locator('div.form-content:visible').click()  # 下拉菜单
                await page.locator(
                    f'div.form-content:visible ul.weui-desktop-dropdown__list li.weui-desktop-dropdown__list-ele:has-text("{self.category}")').first.click()
                await page.wait_for_timeout(1000)
            if await page.locator('button:has-text("声明原创"):visible').count():
                await page.locator('button:has-text("声明原创"):visible').click()

    async def main(self):
        async with async_playwright() as playwright:
            await self.upload(playwright)


path: scripts/uploader/tencent_uploader/__init__.py
content: 


path: scripts/uploader/ks_uploader/__init__.py
content: 


path: scripts/uploader/douyin_uploader/__init__.py
content: 


path: scripts/uploader/bilibili_uploader/__init__.py
content: 


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)