pydoll icon indicating copy to clipboard operation
pydoll copied to clipboard

fingerprint camouflage

Open 3-Tokisaki-Kurumi opened this issue 8 months ago • 43 comments

🔐 Browser Fingerprint Spoofing Feature / 浏览器指纹伪造功能

📋 Summary

This PR introduces a comprehensive browser fingerprint spoofing system that prevents browser fingerprint tracking by generating random but realistic browser fingerprints and injecting them into the browser runtime. The implementation has been updated to use the new centralized options configuration pattern.

🎯 Key Features

Core Components

  • FingerprintGenerator: Generates random but realistic browser fingerprints
  • FingerprintInjector: Injects fingerprints into browsers via JavaScript
  • FingerprintManager: Manages fingerprint generation, storage, and application
  • Fingerprint: Complete data model containing all fingerprint properties
  • FingerprintConfig: Configuration class for customizing fingerprint generation

Browser Support

  • ✅ Chrome/Chromium browsers
  • ✅ Microsoft Edge
  • ✅ Cross-platform compatibility (Windows, macOS, Linux)

Fingerprint Properties

  • 🌐 Navigator Properties: User agent, platform, language, hardware concurrency
  • 🖥️ Screen Properties: Resolution, color depth, viewport dimensions
  • 🎨 WebGL Properties: Vendor, renderer, version, extensions
  • 🎨 Canvas Fingerprinting: Unique canvas rendering signatures
  • 🔊 Audio Context: Sample rate, state, channel count
  • 🌍 Timezone & Locale: Timezone offset, language preferences
  • 🔌 Plugin Information: Browser plugin data
  • 📱 Device Properties: Memory, connection type, touch support

Advanced Features

  • 💾 Persistent Storage: Save and reuse fingerprints
  • 🎲 Entropy Sources: Multiple entropy sources for unique generation
  • ⚙️ Customizable: Extensive configuration options
  • 🔄 Consistent: Generates stable fingerprints across sessions
  • 🎯 Realistic: Uses authentic browser data distributions

🚀 Usage Examples

Basic Usage (New API)

from pydoll.browser.chromium.chrome import Chrome
from pydoll.browser.options import ChromiumOptions

 New centralized options configuration
options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode()
browser = Chrome(options=options)

async with browser:
    tab = await browser.start()
    await tab.go_to("https://fingerprintjs.github.io/fingerprintjs/")

Advanced Configuration

from pydoll.browser.chromium.chrome import Chrome
from pydoll.browser.options import ChromiumOptions
from pydoll.fingerprint import FingerprintConfig

 Create fingerprint configuration
config = FingerprintConfig(
    browser_type="chrome",
    preferred_os="windows",
    enable_webgl_spoofing=True,
    enable_canvas_spoofing=True,
    preferred_languages=["en-US", "en"],
    min_screen_width=1920,
    max_screen_width=2560
)

 Configure browser options
options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode(config)
browser = Chrome(options=options)

Persistent Fingerprints

from pydoll.fingerprint import FingerprintManager

manager = FingerprintManager()
fingerprint = manager.generate_new_fingerprint("chrome")
manager.save_fingerprint("my_identity")

 Later reuse
manager.load_fingerprint("my_identity")

Edge Browser Support

from pydoll.browser.chromium.edge import Edge
from pydoll.browser.options import ChromiumOptions

options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode()
browser = Edge(options=options)

🛡️ Security Benefits

  1. Anti-Detection: Prevents browser fingerprint tracking
  2. Privacy Protection: Masks real browser characteristics
  3. Consistent Identity: Maintains stable fingerprints across sessions
  4. Realistic Spoofing: Uses authentic browser data patterns
  5. Multiple Entropy: Ensures unique fingerprints every time

🔧 Recent Technical Updates

API Migration

  • New Options Pattern: Migrated from direct constructor parameters to centralized ChromiumOptions configuration
  • Improved Modularity: Better separation between browser initialization and feature configuration
  • Consistent Interface: Unified API across Chrome and Edge browsers

Bug Fixes Applied

  • ✅ Fixed string formatting errors in constants.py
  • ✅ Resolved overly broad argument checking in tab.py
  • ✅ Updated all example files to use new API
  • ✅ Standardized test website to https://fingerprintjs.github.io/fingerprintjs/

Enhanced Testing

  • ✅ Updated all test files for new API compatibility
  • ✅ Fixed exception handling in fingerprint injection tests
  • ✅ Improved mock object management in integration tests
  • ✅ Enhanced coverage for edge cases

🧪 Testing & Quality Assurance

  • ✅ Unit Tests: Comprehensive tests for all components
  • ✅ Integration Tests: Real browser testing with Chrome and Edge
  • ✅ API Migration Tests: Validation of new options pattern
  • ✅ Coverage Tests: Targeting specific uncovered code lines
  • ✅ Exception Handling: Robust error handling validation
  • ✅ Cross-Platform: Windows, macOS, Linux compatibility

📁 File Structure

pydoll/fingerprint/
├── __init__.py               Module exports
├── generator.py              Fingerprint generation logic  
├── injector.py               JavaScript injection system
├── manager.py                Fingerprint management
└── models.py                 Data models and configurations

pydoll/browser/
├── options.py                ChromiumOptions with fingerprint support
└── managers/
    └── browser_options_manager.py   Options management

examples/
└── fingerprint_example.py    Updated usage examples

tests/
├── test_fingerprint.py       Core functionality tests
├── test_fingerprint_integration.py   Browser integration tests
├── test_fingerprint_coverage.py      Coverage-specific tests
└── test_exact_coverage.py    Precise line coverage tests

🔧 Technical Implementation Details

  1. Centralized Configuration: New ChromiumOptions.enable_fingerprint_spoofing_mode() API
  2. Options Manager Integration: Seamless integration with ChromiumOptionsManager
  3. JavaScript Injection: Enhanced CDP-based API override system
  4. Realistic Data Generation: Based on authentic browser usage statistics
  5. Robust Error Handling: Graceful degradation when injection fails
  6. Persistent Storage: Improved JSON-based fingerprint storage

🎯 Migration Guide

From Old API to New API:

 OLD (deprecated)
browser = Chrome(enable_fingerprint_spoofing=True)

 NEW (recommended)
options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode()
browser = Chrome(options=options)

📋 概述

此PR引入了一个全面的浏览器指纹伪造系统,通过生成随机但真实的浏览器指纹并将其注入到浏览器运行时来防止浏览器指纹跟踪。实现已更新为使用新的集中式选项配置模式。

🎯 核心功能

核心组件

  • FingerprintGenerator: 生成随机但真实的浏览器指纹
  • FingerprintInjector: 通过JavaScript将指纹注入浏览器
  • FingerprintManager: 管理指纹的生成、存储和应用
  • Fingerprint: 包含所有指纹属性的完整数据模型
  • FingerprintConfig: 用于自定义指纹生成的配置类

🚀 使用示例

基础用法(新API)

from pydoll.browser.chromium.chrome import Chrome
from pydoll.browser.options import ChromiumOptions

 新的集中式选项配置
options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode()
browser = Chrome(options=options)

async with browser:
    tab = await browser.start()
    await tab.go_to("https://fingerprintjs.github.io/fingerprintjs/")

高级配置

from pydoll.fingerprint import FingerprintConfig

config = FingerprintConfig(
    browser_type="chrome",
    preferred_os="windows",
    enable_webgl_spoofing=True,
    enable_canvas_spoofing=True,
    preferred_languages=["zh-CN", "zh", "en-US"],
    min_screen_width=1920,
    max_screen_width=2560
)

options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode(config)
browser = Chrome(options=options)

🔧 最新技术更新

API迁移

  • 新选项模式: 从直接构造函数参数迁移到集中式ChromiumOptions配置
  • 改进的模块化: 更好地分离浏览器初始化和功能配置
  • 统一接口: Chrome和Edge浏览器的统一API

应用的错误修复

  • ✅ 修复了constants.py中的字符串格式错误
  • ✅ 解决了tab.py中过于宽泛的参数检查
  • ✅ 更新所有示例文件使用新API
  • ✅ 标准化测试网站为https://fingerprintjs.github.io/fingerprintjs/

🎯 迁移指南

从旧API到新API:

 旧方式(已弃用)
browser = Chrome(enable_fingerprint_spoofing=True)

 新方式(推荐)
options = ChromiumOptions()
options.enable_fingerprint_spoofing_mode()
browser = Chrome(options=options)

🏷️ Labels

  • feature - New feature
  • security - Security enhancement
  • privacy - Privacy protection
  • browser - Browser automation
  • fingerprint - Fingerprint spoofing
  • api-migration - API modernization

📊 Metrics

  • Lines of Code: ~2000+ lines
  • Test Coverage: 95%+
  • Supported Browsers: Chrome, Edge
  • Configuration Options: 15+
  • Fingerprint Properties: 25+
  • Test Files Updated: 4 files
  • Bug Fixes Applied: 5 major fixes

🎉 Summary

This feature adds comprehensive browser fingerprint protection capabilities to pydoll, enabling users to easily prevent fingerprint tracking while maintaining natural and consistent browser behavior. The updated API provides better modularity and easier integration, supporting both simple one-click enabling and complex custom configurations to meet different user needs.

3-Tokisaki-Kurumi avatar May 10 '25 03:05 3-Tokisaki-Kurumi

Codecov Report

:x: Patch coverage is 99.44751% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pydoll/browser/chromium/base.py 97.05% 1 Missing :warning:
pydoll/browser/managers/browser_options_manager.py 96.87% 1 Missing :warning:
pydoll/browser/options.py 94.44% 1 Missing :warning:

:loudspeaker: Thoughts on this report? Let us know!

codecov[bot] avatar May 10 '25 04:05 codecov[bot]

这些代码风格问题(空行空格、文件末尾换行等)不影响功能实现。考虑到当前 PR 的主要目的是添加指纹伪装功能,建议先审查功能有效性,这些微小的格式问题可以在后续优化。

3-Tokisaki-Kurumi avatar May 10 '25 04:05 3-Tokisaki-Kurumi

只是测试分支没有测试到,但是不影响使用

3-Tokisaki-Kurumi avatar May 10 '25 06:05 3-Tokisaki-Kurumi

Hi @3-Tokisaki-Kurumi , thank you so much for you contribution!

To maintain consistency with the rest of the code, we only need to translate what's necessary into English

thalissonvs avatar May 15 '25 10:05 thalissonvs

你好@3-Tokisaki-Kurumi,非常感谢您的贡献!

为了与其余代码保持一致,我们只需要将必要的内容翻译成英语

Thank you for your attention. I will translate the necessary content into English

3-Tokisaki-Kurumi avatar May 15 '25 12:05 3-Tokisaki-Kurumi

你好@3-Tokisaki-Kurumi,非常感谢您的贡献!

为了与其余代码保持一致,我们只需要将必要的内容翻译成英语

现在我已经将必要的内容和注释翻译成英语,您可以审阅一下,之后我会继续贡献更优化的代码

Now I have translated the necessary content and comments into English, you can review them, and I will continue to contribute more optimized code afterwards

3-Tokisaki-Kurumi avatar May 17 '25 03:05 3-Tokisaki-Kurumi

Hi @3-Tokisaki-Kurumi,

Sorry for the delay. I'm currently finalizing version 2 of Pydoll, which will introduce breaking changes. Once it's done, we can move forward with your contribution, as a few more adjustments will be needed. I appreciate your understanding

thalissonvs avatar May 28 '25 06:05 thalissonvs

你好@3-Tokisaki-Kurumi,

抱歉耽搁了。我目前正在最终完成 Pydoll 的 2.0 版本,这将引入一些重大更改。完成后,我们就可以继续处理您的贡献,因为还需要进行一些调整。感谢您的理解。

OK,期待你的Pydoll v2,感谢您带来了一个强大的自动化工具,等待你的更改之后我们可以继续处理此贡献

OK, Looking forward to your Pydoll v2. Thank you for bringing us a powerful automation tool. We are waiting for your changes before we can proceed with this contribution

3-Tokisaki-Kurumi avatar May 28 '25 07:05 3-Tokisaki-Kurumi

占位,准备继续完成该贡献

Position occupied, ready to continue completing the contribution

3-Tokisaki-Kurumi avatar Jun 09 '25 04:06 3-Tokisaki-Kurumi

你好@3-Tokisaki-Kurumi,

抱歉耽搁了。我目前正在最终完成 Pydoll 的 2.0 版本,这将引入一些重大更改。完成后,我们就可以继续处理您的贡献,因为还需要进行一些调整。感谢您的理解。

现在是最新的指纹伪装功能,你可以查看一下代码,如果需要,之后我会继续贡献

Now it's the latest fingerprint camouflage feature. You can review the code and if needed, I will continue to contribute in the future

3-Tokisaki-Kurumi avatar Jun 13 '25 08:06 3-Tokisaki-Kurumi

Hello! Once again, thank you very much for your efforts, I truly appreciate your contribution. However, I just noticed one thing: you didn't follow the Commitizen convention, which is required for release generation.

Before I can review your PR, could you please update your commit history to follow the guidelines described in this link?

You can do that by running the following commands:

git rebase -i HEAD~<number_of_commits_to_edit>
# Replace 'pick' with 'reword' for each commit
# Then rewrite each commit message using the Commitizen format

After that, force-push your changes. Also, everything must be in english. Thank you!

thalissonvs avatar Jun 14 '25 23:06 thalissonvs

@thalissonvs Hello, thank you for your correction. I have updated my submission history to follow this [link]( https://github.com/autoscrape-labs/pydoll/blob/main/CONTRIBUTING.md )The guidelines described in the document have been forcibly pushed, and now you can review my PR. Thank you for your review

您好,感谢您的纠正,我已经更新了我的的提交历史记录以遵循此链接中描述的指南并且进行了强制推送,接下来您可以审查我的PR,感谢您的审查

3-Tokisaki-Kurumi avatar Jun 15 '25 09:06 3-Tokisaki-Kurumi

Hi @3-Tokisaki-Kurumi, I left some RC's that need to be resolved.

Also, it seems that your PR doesn’t reflect the new file structure introduced in the second version. If you need any help, I can implement some of the changes for you.

thalissonvs avatar Jun 18 '25 03:06 thalissonvs

您好,感谢您的指正,我会根据您的建议修改,如果您能提供一些帮助,我将感激不尽,麻烦您了

Hello, thank you for your correction. If you could provide some help, I would be extremely grateful. Thank you very much

3-Tokisaki-Kurumi avatar Jun 18 '25 14:06 3-Tokisaki-Kurumi

你好@3-Tokisaki-Kurumi,我留下了一些需要解决的RC。

另外,你的 PR 似乎不支持出第二版本中引入的新文件结构。如果你需要帮助,我可以帮助实现一些更改。

进行了一些变更,请您查看一下

Some changes have been made, please take a look

3-Tokisaki-Kurumi avatar Jun 19 '25 10:06 3-Tokisaki-Kurumi

您好,感谢您的指正,我会根据您的建议修改,如果您能提供一些帮助,我将感激不尽,麻烦您了

Hello, thank you for your correction. If you could provide some help, I would be extremely grateful. Thank you very much

Sure, I'll help you on this weekend. Thanks for your efforts

thalissonvs avatar Jun 20 '25 00:06 thalissonvs

您好,感谢您的建议,我会根据您的修改,如果能为您提供一些帮助,我将感激不尽,麻烦您了

您好,感谢您的指正。如果您能提供一些帮助,我将不胜感激。非常感谢

当然,这个周末我会帮你。谢谢你的努力

感谢您能提供帮助,另外我观察到最近的更新中对于当前的websockets库对新版本不兼容导致了报错已经得到了解决,还有我解决了现有的冲突

Thank you for providing assistance. Additionally, I have noticed that the recent update has resolved the issue of errors caused by the current websocket library being incompatible with the new version,And I resolved the existing conflict

3-Tokisaki-Kurumi avatar Jun 22 '25 07:06 3-Tokisaki-Kurumi

bugbot run

thalissonvs avatar Jun 27 '25 02:06 thalissonvs

Bugbot 运行

哇哦,酷,这个为我找到了一些代码潜在的风险,我进行了一些修改并提升了测试覆盖率

Wow, cool. This has identified some potential risks in my code, and I have made some modifications to improve the testing coverage

3-Tokisaki-Kurumi avatar Jun 30 '25 11:06 3-Tokisaki-Kurumi

Hey! I think we should take a step back and try again. We need a clean commit history, 100% in English and following the Commitizen style. My suggestion would be for you to open a new branch and work from there, keeping only the essentials and making sure to follow the standard properly

thalissonvs avatar Jul 03 '25 23:07 thalissonvs

嘿!我觉得我们应该先退一步再试。 我们需要一个干净的提交历史,100% 英文,并且遵循 Commitizen 的风格。 我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

嘿!我觉得我们应该先退一步再试。 我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。 我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

Do you want me to resubmit the pull once, or create a new branch based on my current pull repository? Can you help me with something

是要我重新提交拉取一次,还是在我现在拉取仓库的基础上创建一个新的分支?您能帮助我一些吗

3-Tokisaki-Kurumi avatar Jul 04 '25 03:07 3-Tokisaki-Kurumi

嘿!我觉得我们应该先退一步再试。 我们需要一个干净的提交历史,100% 英文,并且遵循 Commitizen 的风格。 我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

嘿!我觉得我们应该先退一步再试。 我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。 我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

Do you want me to resubmit the pull once, or create a new branch based on my current pull repository? Can you help me with something

是要我重新提交拉取一次,还是在我现在拉取仓库的基础上创建一个新的分支?您能帮助我一些吗

In my opinion, we should create a new branch from main and start the changes over. I can help you with that. Open a pull request with just a few initial changes and a clean commit history, and I’ll help you with the next steps, sounds good?

thalissonvs avatar Jul 05 '25 03:07 thalissonvs

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

你是想让我重新提交一次拉取请求,还是基于我当前的拉取仓库创建一个新的分支?你能帮我个忙吗? 是要我重新提交拉取一次,还是在我现在拉取仓库的基础上创建一个新的分支?您能帮助我一些吗?

我认为我们应该从主分支创建一个新的分支,然后重新开始修改。我可以帮你。只需提交一些初始修改和干净的提交历史记录,然后创建一个拉取请求,我会帮你完成接下来的步骤,听起来不错吧?

3-Tokisaki-Kurumi avatar Jul 05 '25 06:07 3-Tokisaki-Kurumi

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

你是想让我重新提交一次拉取请求,还是基于我当前的拉取仓库创建一个新的分支?你能帮我个忙吗? 是要我重新提交拉取一次,还是在我现在拉取仓库的基础上创建一个新的分支?您可以帮助我一些吗?

我认为我们应该从主分支创建一个新的分支,然后重新开始修改。我可以帮你。只需提交一些初始修改和干净的提交历史记录,然后创建一个拉取请求,我会帮你完成接下来的步骤,听起来不错吧?

I have found an optimal solution. Could you please review it again 我找到了一个最优解,麻烦你重新审核一下 @thalissonvs

3-Tokisaki-Kurumi avatar Jul 05 '25 08:07 3-Tokisaki-Kurumi

您好!再次感谢您的努力,我由衷地感谢您的贡献。但是,我注意到一件事:您没有遵循 Commitizen 的约定,而这是生成发布版本所必需的。

在我审查您的 PR 之前,您能否更新您的提交历史记录以遵循此链接中描述的指南?

您可以通过运行以下命令来实现:

git rebase -i HEAD~<number_of_commits_to_edit>
# Replace 'pick' with 'reword' for each commit
# Then rewrite each commit message using the Commitizen format

之后,强制推送你的更改。另外,所有内容必须使用英文。谢谢!

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

你是想让我重新提交一次拉取请求,还是基于我当前的拉取仓库创建一个新的分支?你能帮我个忙吗? 是要我重新提交拉取一次,还是在我现在拉取仓库的基础上创建一个新的分支?您能帮助我一些吗?

我认为我们应该从主分支创建一个新的分支,然后重新开始修改。我可以帮你。只需提交一些初始修改和干净的提交历史记录,然后创建一个拉取请求,我会帮你完成接下来的步骤,听起来不错吧?

需要的更改实在是很多诶 There are a lot of changes needed

3-Tokisaki-Kurumi avatar Jul 12 '25 05:07 3-Tokisaki-Kurumi

您好!再次感谢您的努力,我衷心感谢您的贡献。,我注意到一件事:您没有遵循 Commitizen 的约定,而这是生成发布版本所必需的。 在我审查您的 PR 之前,您是否可以按照此链接中描述的指南更新您的提交历史记录? 您可以通过运行以下命令来实现:

git rebase -i HEAD~<number_of_commits_to_edit>
# Replace 'pick' with 'reword' for each commit
# Then rewrite each commit message using the Commitizen format

此后,请强制您进行更改。另外,所有内容必须使用英文。谢谢!

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

嘿!我觉得我们应该先退一步再试。我们需要一个完整的工作历史,100%中文,并且遵循 Commitizen 的风格。我建议你创建一个新的分支,从那里开始工作,只保留必要的内容,并确保正确遵循标准。

您是要我重新提交一次拉取请求,还是根据我当前的拉取仓库创建一个新的分支?您能帮我个忙吗? 是要我重新提交一次拉取请求,还是在我现在拉取仓库的基础上创建一个新的分支?能帮我吗?

我认为我们应该从主分支创建一个新的分支,然后重新开始修改。我可以帮忙。只需提交一些最初且完整的提交历史修改记录,然后创建一个拉取请求,我就可以帮忙完成接下来的步骤,听起来吧?

我想到了一个新的办法

I have come up with a new approach

3-Tokisaki-Kurumi avatar Jul 12 '25 06:07 3-Tokisaki-Kurumi

@thalissonvs 嘿!我重新提交了一份符合贡献规范的pr,请你审查一下

hello!well! I have resubmitted a PR that meets the contribution criteria. Please review it

3-Tokisaki-Kurumi avatar Jul 12 '25 07:07 3-Tokisaki-Kurumi

Hey, I am hoping we can merge and release a new version with this PR today? I really need it quick. Let me know if any help is required

haris-musa avatar Jul 12 '25 11:07 haris-musa

嘿,我希望我们今天能用这个 PR 合并发布一个新版本?我真的很急。如果需要帮助,请告诉我。

hey,For this PR merge, I think you may need to ask@thalissonvs ,You can also go to my warehouse first https://github.com/3-Tokisaki-Kurumi/pydoll-enhance Download source code temporarily for emergency use

3-Tokisaki-Kurumi avatar Jul 12 '25 12:07 3-Tokisaki-Kurumi

@thalissonvs This is a clean and compliant submission history record

3-Tokisaki-Kurumi avatar Jul 13 '25 17:07 3-Tokisaki-Kurumi