commit 63bfeca740ab72a5b3c870f336666dbe0bfe47d5 Author: dela Date: Tue Jan 13 11:28:37 2026 +0800 解混淆 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f97e45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# IDE and Editor Files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# OS Files +Thumbs.db +desktop.ini + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Extension Build Files +*.zip +*.crx +*.pem + +# Temporary Files +*.tmp +*.temp +.cache/ + +# Node modules (if added in future) +node_modules/ +package-lock.json +yarn.lock + +# Test Files +test-results/ +coverage/ + +# Private Keys and Certificates +*.key +*.pem +*.p12 +secrets.json + +# Environment Files +.env +.env.local +.env.*.local + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9efaa5c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,145 @@ +# Contributing to cardbingenerator - Stripe Auto Fill + +First off, thank you for considering contributing to cardbingenerator! It's people like you that make cardbingenerator such a great tool. + +## Code of Conduct + +This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check the existing issues as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible: + +* **Use a clear and descriptive title** +* **Describe the exact steps which reproduce the problem** +* **Provide specific examples to demonstrate the steps** +* **Describe the behavior you observed after following the steps** +* **Explain which behavior you expected to see instead and why** +* **Include screenshots and animated GIFs** if possible +* **Include your browser version and OS** + +### Suggesting Enhancements + +Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion, please include: + +* **Use a clear and descriptive title** +* **Provide a step-by-step description of the suggested enhancement** +* **Provide specific examples to demonstrate the steps** +* **Describe the current behavior** and **explain which behavior you expected to see instead** +* **Explain why this enhancement would be useful** + +### Pull Requests + +* Fill in the required template +* Do not include issue numbers in the PR title +* Include screenshots and animated GIFs in your pull request whenever possible +* Follow the JavaScript style guide +* End all files with a newline +* Avoid platform-dependent code + +## Development Process + +### Setup + +1. Fork the repository +2. Clone your fork: +```bash +git clone https://github.com/your-username/cardbingenerator-extension.git +``` +3. Create a branch: +```bash +git checkout -b feature/your-feature-name +``` + +### Making Changes + +1. Make your changes +2. Test your changes thoroughly: + - Load extension in developer mode + - Test on multiple Stripe checkout pages + - Verify all functionality works + - Check console for errors + +3. Commit your changes: +```bash +git add . +git commit -m "Add: description of your changes" +``` + +### Commit Message Guidelines + +* Use the present tense ("Add feature" not "Added feature") +* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +* Limit the first line to 72 characters or less +* Reference issues and pull requests liberally after the first line +* Consider starting the commit message with an applicable emoji: + * 🎨 `:art:` when improving the format/structure of the code + * 🐛 `:bug:` when fixing a bug + * ✨ `:sparkles:` when adding a new feature + * 📝 `:memo:` when writing docs + * 🚀 `:rocket:` when improving performance + * 🔒 `:lock:` when dealing with security + * ⬆️ `:arrow_up:` when upgrading dependencies + * ⬇️ `:arrow_down:` when downgrading dependencies + * 🔧 `:wrench:` when changing configuration files + +### Coding Style + +* Use 2 spaces for indentation +* Use camelCase for variable and function names +* Use PascalCase for class names +* Use UPPER_CASE for constants +* Add comments for complex logic +* Keep functions small and focused +* Use meaningful variable names +* Avoid magic numbers +* Use template literals for string concatenation + +### Testing Checklist + +Before submitting your PR, ensure: + +- [ ] Code follows the style guidelines +- [ ] No console errors +- [ ] Extension loads without errors +- [ ] All existing features still work +- [ ] New features work as expected +- [ ] Tested on multiple Stripe pages +- [ ] No performance degradation +- [ ] Code is commented where necessary +- [ ] No hardcoded values (use constants) + +### Pull Request Process + +1. Update the README.md with details of changes if applicable +2. Update the CHANGELOG.md with a note describing your changes +3. The PR will be merged once you have the sign-off of a maintainer + +## File Structure + +``` +cardbingenerator-extension/ +├── manifest.json # Extension configuration +├── popup.html # Popup UI +├── popup.js # Popup logic +├── styles.css # Styles +├── background.js # Service worker +├── content.js # Content script +├── icons/ # Extension icons +├── README.md # Documentation +├── LICENSE # License file +└── CONTRIBUTING.md # This file +``` + +## Questions? + +Feel free to open an issue with your question or contact the maintainers directly. + +## Recognition + +Contributors will be recognized in the README.md file. + +Thank you for contributing! 🎉 + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4c61eb1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 cardbingenerator - Stripe Auto Fill + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a887723 --- /dev/null +++ b/README.md @@ -0,0 +1,546 @@ +# 💳 Cardbingenerator - Stripe Auto Fill + +[![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/yourusername/cardbingenerator-extension) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) +[![Chrome Extension](https://img.shields.io/badge/Chrome-Extension-green.svg)](https://developer.chrome.com/docs/extensions/) + +一个功能强大的 Chrome 浏览器扩展,用于自动生成测试信用卡号并自动填充 Stripe 支付表单。支持 Luhn 算法验证、自定义地址管理和现代化暗黑主题界面。 + +[English](#english-version) | [中文](#中文版本) + +--- + +## 中文版本 + +## ⚠️ 免责声明 + +**本工具仅用于测试和教育目的** + +- 仅可用于开发/测试环境或演示支付表单 +- 请勿在生产环境的支付系统中使用 +- 请勿用于欺诈或任何非法活动 +- 开发者对工具的滥用不承担任何责任 +- 测试卡号无法在真实支付处理器上使用 + +## 🌟 核心特性 + +### 💳 智能卡号生成 +- **Luhn 算法验证**:生成数学上有效的卡号 +- **BIN 支持**:使用任意银行识别号码(BIN)模式 +- **多卡种识别**:自动识别 Visa、Mastercard、Amex、Discover 等 +- **智能生成**:包含有效的到期日期和 CVV 安全码 + +### 🚀 自动填充能力 +- **智能字段检测**:自动查找并填充卡片字段 +- **Shadow DOM 支持**:兼容现代 Web 组件 +- **地址自动填充**:填充账单地址、城市、州、邮编 +- **仿人类输入**:模拟自然的打字模式 +- **框架兼容**:绕过 React/Vue 值设置器 + +### 🛠️ 高度定制化 +- **自定义 BIN 历史**:保存并重用你喜欢的 BIN 模式 +- **多地址管理**:管理自定义账单地址 +- **姓名生成器**:内置身份池用于持卡人姓名 +- **数据源选择**:在静态、手动或自动生成数据之间切换 + +### 🔒 隐私保护功能 +- **数据清理**:深度清除 cookies、缓存和存储 +- **反指纹追踪**:清除所有 Stripe 相关的浏览数据 +- **隔离存储**:所有数据本地存储在浏览器中 + +## 📦 安装方法 + +### 从源码安装 + +1. **克隆仓库** + ```bash + git clone https://github.com/yourusername/cardbingenerator.git + cd cardbingenerator + ``` + +2. **在 Chrome 中加载扩展** + - 打开 Chrome 并访问 `chrome://extensions/` + - 启用"开发者模式"(右上角的开关) + - 点击"加载已解压的扩展程序" + - 选择项目文件夹 + +3. **固定扩展图标** + - 点击 Chrome 工具栏中的拼图图标 + - 找到"cardbingenerator"并点击图钉图标 + +### 手动安装 + +1. 下载最新版本 +2. 解压 ZIP 文件 +3. 按照"从源码安装"中的步骤 2-3 操作 + +## 🚀 使用指南 + +### 基本使用 + +1. **打开扩展** + - 点击 Chrome 工具栏中的扩展图标 + - 你会看到主界面,包含标签页:General、BIN List、Settings + +2. **输入 BIN 模式** + - 在"BIN Template"字段中输入 BIN(例如:`552461`) + - 扩展会自动用 'x' 填充到 16 位 + - 示例:`552461` 会变成 `552461xxxxxxxxxx` + +3. **生成并填充** + - 导航到 Stripe 结账页面 + - 点击"Fill Everything"按钮 + - 扩展将自动: + - 生成有效的卡号 + - 填充卡片详情(卡号、到期日、CVV) + - 填充账单地址信息 + - 自动提交表单 + +### 高级功能 + +#### BIN 历史记录 +- 点击 BIN 输入框旁边的 `+` 按钮保存到历史 +- 点击任意已保存的 BIN 快速选择 +- 点击 `×` 从历史中删除 + +#### 自定义地址 +1. 进入 **Settings** → **Addresses** 标签 +2. 填写地址表单字段 +3. 点击"Add"保存 +4. 在 Address Source 下拉菜单中选择"Manual (Custom)" + +#### 设置配置 + +**卡号验证开关** +- 启用:生成通过 Luhn 算法验证的卡号 +- 禁用:生成随机卡号(更快,较宽松) + +**地址来源** +- **Static**:使用内置默认地址 +- **Manual**:使用你自定义保存的地址 +- **Auto**:生成随机地址(即将推出) + +**姓名来源** +- 控制是使用内置姓名还是自定义条目 +- 姓名从地址条目中提取 + +## 🎯 工作原理 + +### 卡号生成算法 + +扩展使用 **Luhn 算法**(模 10 校验和)生成有效卡号: + +1. 接收你的 BIN 模式(例如:`552461xxxxxxxxxx`) +2. 为每个 `x` 填充随机数字 +3. 计算最后一位的 Luhn 校验位 +4. 验证整个卡号 + +```javascript +// 生成示例 +BIN 输入: 552461xxxxxxxxxx +生成结果: 5524610123456789 +验证状态: ✅ 通过 Luhn 验证 +卡片类型: Mastercard +``` + +### 自动填充流程 + +1. **字段检测**:扫描页面查找卡片和地址字段 + - 使用 CSS 选择器和 autocomplete 属性 + - 穿透 Shadow DOM 边界 + - 根据相关性为字段评分 + +2. **智能填充**:按最佳顺序填充字段 + - 首先填充国家(可能会刷新表单结构) + - 使用模拟打字填充卡片详情 + - 适当延迟填充地址字段 + +3. **事件触发**:触发适当的 DOM 事件 + - 原生值设置器绕过框架锁定 + - Input、change 和 blur 事件 + - 焦点管理触发验证 + +## 🔧 技术细节 + +### 支持的 Stripe 版本 +- Stripe Elements v3 +- Stripe Checkout +- 自定义 Stripe 集成 + +### 浏览器兼容性 +- Chrome 88+ +- Edge 88+ +- Brave(基于 Chromium) +- Opera(基于 Chromium) + +### 使用的权限 + +| 权限 | 用途 | +|------------|---------| +| `storage` | 保存 BIN 历史和自定义地址 | +| `activeTab` | 与当前 Stripe 结账页面交互 | +| `tabs` | 查找和管理 Stripe 标签页 | +| `scripting` | 注入自动填充脚本 | +| `cookies` | 清除会话数据用于测试 | +| `browsingData` | 深度清除 Stripe 指纹数据 | + +### 文件结构 + +``` +cardbingenerator/ +├── manifest.json # 扩展配置 +├── background.js # Service Worker(卡号生成逻辑) +├── content.js # Content Script(自动填充逻辑) +├── popup.html # 扩展用户界面 +├── popup.js # UI 逻辑和设置 +├── styles.css # 扩展样式 +├── icon16.png # 扩展图标(16x16) +├── icon48.png # 扩展图标(48x48) +├── icon128.png # 扩展图标(128x128) +└── README.md # 本文件 +``` + +## 🎨 支持的卡片类型 + +扩展自动检测并生成以下卡片: + +- ✅ Visa(以 4 开头) +- ✅ Mastercard(以 51-55 开头) +- ✅ American Express(以 34、37 开头) +- ✅ Discover(以 6011、65 开头) +- ✅ JCB(以 35 开头) +- ✅ Diners Club(以 30、36、38 开头) +- ✅ Maestro(以 50、56-58、6304、6390、67 开头) +- ✅ UnionPay(以 62 开头) + +## 🐛 故障排除 + +### 扩展未填充表单 +- **解决方案**:确保你在 Stripe 结账页面上 +- 检查页面是否已完全加载 +- 尝试刷新页面并再次点击"Fill Everything" + +### 生成的卡号被拒绝 +- **解决方案**:在设置中启用"Card Validation" +- 某些测试环境需要 Luhn 有效的卡号 +- 如果一个 BIN 不起作用,尝试使用不同的 BIN + +### 字段填充不正确 +- **解决方案**:页面可能有自定义字段名称 +- 尝试点击"Clear All Data"按钮并刷新 +- 报告问题时请提供页面 URL + +### 扩展图标未出现 +- **解决方案**:检查扩展是否在 `chrome://extensions/` 中启用 +- 尝试重新加载扩展 +- 检查浏览器控制台是否有错误 + +## 🔐 安全说明 + +### 仅本地存储 +- 所有数据存储在 Chrome 本地存储中 +- 敏感数据不调用外部 API +- 无遥测或跟踪 + +### 数据清理 +使用"Clear All Data"按钮清除: +- Cookies(包括 HttpOnly) +- LocalStorage 和 SessionStorage +- IndexedDB 数据库 +- 缓存和 Service Workers +- Stripe 指纹数据 + +## 📚 资源链接 + +- [Stripe 测试文档](https://stripe.com/docs/testing) +- [Luhn 算法详解](https://zh.wikipedia.org/wiki/Luhn%E7%AE%97%E6%B3%95) +- [Chrome 扩展开发](https://developer.chrome.com/docs/extensions/) + +## 🤝 贡献指南 + +欢迎贡献!请遵循以下指南: + +1. Fork 仓库 +2. 创建特性分支 (`git checkout -b feature/amazing-feature`) +3. 提交更改 (`git commit -m 'Add amazing feature'`) +4. 推送到分支 (`git push origin feature/amazing-feature`) +5. 打开 Pull Request + +### 开发设置 + +```bash +# 克隆你的 fork +git clone https://github.com/yourusername/cardbingenerator.git + +# 创建分支 +git checkout -b my-feature + +# 进行更改并在 Chrome 中测试 +# 从 chrome://extensions 加载未打包的扩展 + +# 提交并推送 +git add . +git commit -m "你的功能描述" +git push origin my-feature +``` + +## 📄 许可证 + +本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件 + +## ⚠️ 法律声明 + +本软件按"原样"提供,不提供任何形式的保证。作者和贡献者: + +- 不支持欺诈活动 +- 不鼓励绕过支付安全 +- 对滥用不承担责任 +- 建议仅在授权测试环境中使用 + +**使用风险和责任自负** + +## 👨‍💻 作者 + +由 **Blackhat_bullet** 创建 + +- 📺 [YouTube 频道](https://www.youtube.com/@Blackhat_bullet) +- 💬 [Telegram 群组](https://t.me/+VJJt9csJoEUxZTA1) +- 📞 [临时短信服务](https://t.me/Tempotpsms_bot) +- 💳 [BIN 生成器网站](https://cardbingenerator.com/) + +## 🙏 致谢 + +- Stripe 提供的优秀测试文档 +- Chrome 扩展社区 +- 所有贡献者和测试人员 + +--- + +
+ 记住:本工具仅用于测试目的。请始终尊重法律和你正在测试的平台的服务条款。 +
+ +--- + +## English Version + +## ⚠️ Disclaimer + +**This tool is for TESTING and EDUCATIONAL purposes only.** + +- Use only on development/test environments or demo payment forms +- Do NOT use on production payment systems +- Do NOT use for fraud or any illegal activities +- The developers assume NO responsibility for misuse of this tool +- Test cards will not work on real payment processors + +## 🌟 Features + +### 💳 Smart Card Generation +- **Luhn Algorithm Validation**: Generates mathematically valid card numbers +- **BIN Support**: Use any Bank Identification Number (BIN) pattern +- **Multiple Card Types**: Automatically detects Visa, Mastercard, Amex, Discover, etc. +- **Smart Generation**: Includes valid expiry dates and CVV codes + +### 🚀 Auto-Fill Capabilities +- **Intelligent Field Detection**: Automatically finds and fills card fields +- **Shadow DOM Support**: Works with modern web components +- **Address Auto-Fill**: Fills billing address, city, state, ZIP code +- **Human-Like Input**: Simulates natural typing patterns +- **Framework Compatible**: Bypasses React/Vue value setters + +### 🛠️ Customization +- **Custom BIN History**: Save and reuse your favorite BIN patterns +- **Multiple Addresses**: Manage custom billing addresses +- **Name Generator**: Built-in identity pool for cardholder names +- **Source Selection**: Choose between static, manual, or auto-generated data + +### 🔒 Privacy Features +- **Data Clearing**: Deep clean cookies, cache, and storage +- **Anti-Fingerprinting**: Clear all Stripe-related browsing data +- **Isolated Storage**: All data stored locally in your browser + +## 📦 Installation + +### From Source + +1. **Clone the repository** + ```bash + git clone https://github.com/yourusername/cardbingenerator.git + cd cardbingenerator + ``` + +2. **Load the extension in Chrome** + - Open Chrome and go to `chrome://extensions/` + - Enable "Developer mode" (toggle in top right) + - Click "Load unpacked" + - Select the project folder + +3. **Pin the extension** + - Click the puzzle icon in Chrome toolbar + - Find "cardbingenerator" and click the pin icon + +## 🚀 Usage + +### Basic Usage + +1. **Open the Extension** + - Click the extension icon in your Chrome toolbar + - You'll see the main interface with tabs: General, BIN List, Settings + +2. **Enter a BIN Pattern** + - In the "BIN Template" field, enter a BIN (e.g., `552461`) + - The extension auto-fills with 'x' to create a 16-digit pattern + - Example: `552461` becomes `552461xxxxxxxxxx` + +3. **Generate & Fill** + - Navigate to a Stripe checkout page + - Click "Fill Everything" button + - The extension will: + - Generate a valid card number + - Fill card details (number, expiry, CVV) + - Fill billing address information + +### Advanced Features + +#### BIN History +- Click the `+` button next to BIN input to save to history +- Click any saved BIN to quickly select it +- Click `×` to remove from history + +#### Custom Addresses +1. Go to **Settings** → **Addresses** tab +2. Fill in the address form fields +3. Click "Add" to save +4. Select "Manual (Custom)" in the Address Source dropdown + +#### Settings Configuration + +**Card Validation Toggle** +- Enable: Generates cards that pass Luhn algorithm validation +- Disable: Generates random cards (faster, less strict) + +**Address Source** +- **Static**: Use built-in default addresses +- **Manual**: Use your custom saved addresses +- **Auto**: Generate random addresses (coming soon) + +**Name Source** +- Controls whether to use built-in names or custom entries +- Names are taken from the address entries + +## 🎯 How It Works + +### Card Number Generation + +The extension uses the **Luhn Algorithm** (mod-10 checksum) to generate valid card numbers: + +1. Takes your BIN pattern (e.g., `552461xxxxxxxxxx`) +2. Fills random digits for each `x` +3. Calculates the Luhn check digit for the last position +4. Validates the entire card number + +```javascript +// Example generated card +BIN Input: 552461xxxxxxxxxx +Generated: 5524610123456789 +Valid: ✅ Passes Luhn validation +Type: Mastercard +``` + +### Auto-Fill Process + +1. **Field Detection**: Scans the page for card and address fields + - Uses CSS selectors and autocomplete attributes + - Penetrates Shadow DOM boundaries + - Scores fields by relevance + +2. **Smart Filling**: Fills fields in optimal order + - Country first (may refresh form structure) + - Card details with simulated typing + - Address fields with proper delays + +3. **Event Triggering**: Fires proper DOM events + - Native value setters bypass framework locks + - Input, change, and blur events + - Focus management for validation triggers + +## 🎨 Supported Card Types + +The extension automatically detects and generates cards for: + +- ✅ Visa (starts with 4) +- ✅ Mastercard (starts with 51-55) +- ✅ American Express (starts with 34, 37) +- ✅ Discover (starts with 6011, 65) +- ✅ JCB (starts with 35) +- ✅ Diners Club (starts with 30, 36, 38) +- ✅ Maestro (starts with 50, 56-58, 6304, 6390, 67) +- ✅ UnionPay (starts with 62) + +## 🐛 Troubleshooting + +### Extension doesn't fill the form +- **Solution**: Make sure you're on a Stripe checkout page +- Check that the page has loaded completely +- Try refreshing the page and clicking "Fill Everything" again + +### Generated cards are rejected +- **Solution**: Enable "Card Validation" in settings +- Some test environments require Luhn-valid cards +- Use a different BIN if one doesn't work + +### Fields are filled incorrectly +- **Solution**: The page may have custom field names +- Try clicking the "Clear All Data" button and refresh +- Report the issue with the page URL + +### Extension icon doesn't appear +- **Solution**: Check if extension is enabled in `chrome://extensions/` +- Try reloading the extension +- Check browser console for errors + +## 🔐 Security Notes + +### Local Storage Only +- All data stored locally in Chrome storage +- No external API calls for sensitive data +- No telemetry or tracking + +### Data Clearing +Use the "Clear All Data" button to remove: +- Cookies (including HttpOnly) +- LocalStorage and SessionStorage +- IndexedDB databases +- Cache and Service Workers +- Stripe fingerprinting data + +## 👨‍💻 Author + +Created by **Blackhat_bullet** + +- 📺 [YouTube Channel](https://www.youtube.com/@Blackhat_bullet) +- 💬 [Telegram Group](https://t.me/+VJJt9csJoEUxZTA1) +- 📞 [Temp SMS Service](https://t.me/Tempotpsms_bot) +- 💳 [BIN Generator Website](https://cardbingenerator.com/) + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## ⚠️ Legal Notice + +This software is provided "as is" without warranty of any kind. The authors and contributors: + +- Do NOT endorse fraudulent activities +- Do NOT encourage bypassing payment security +- Do NOT take responsibility for misuse +- Recommend using only in authorized test environments + +**Use at your own risk and responsibility.** + +--- + +
+ Remember: This tool is for testing purposes only. Always respect the law and terms of service of the platforms you're testing on. +
diff --git a/background.js b/background.js new file mode 100644 index 0000000..d3cb7f7 --- /dev/null +++ b/background.js @@ -0,0 +1,550 @@ +/** + * 虚拟身份池 + * 用于生成随机持卡人姓名 + */ +const FIRST_NAMES = ["John", "Michael", "David", "James", "Robert", "William", "Richard", "Joseph", "Charles", "Thomas", "Christopher", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", "Paul", "Andrew", "Joshua", "Kenneth", "Kevin", "Brian", "Mary", "Patricia", "Jennifer", "Linda", "Barbara", "Elizabeth", "Susan", "Jessica", "Sarah", "Karen", "Nancy", "Lisa", "Betty", "Margaret", "Sandra"]; +const LAST_NAMES = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young"]; + +/** + * 默认地址配置 + */ +const DEFAULT_ADDRESSES = [{ + name: "John Smith", + firstName: "John", + lastName: "Smith", + address1: "69 Adams Street", + address2: "", + city: "Brooklyn", + state: "New York", + stateCode: "NY", + postal: "11201", + countryText: "United States", + countryValue: "US" +}, { + name: "Michael Johnson", + firstName: "Michael", + lastName: "Johnson", + address1: "3511 Carlisle Avenue", + address2: "", + city: "Covington", + state: "Kentucky", + stateCode: "KY", + postal: "41015", + countryText: "United States", + countryValue: "US" +}]; + +// ========================================== +// 算法工具函数 (Luhn Algorithm) +// ========================================== + +function randomChoice(array) { + return array[Math.floor(Math.random() * array.length)]; +} + +/** + * 根据卡号前缀识别卡组织类型 + */ +function getCardType(cardNumber) { + const patterns = { + Visa: /^4/, + Mastercard: /^5[1-5]/, + "American Express": /^3[47]/, + Discover: /^6(?:011|5)/, + JCB: /^35/, + "Diners Club": /^3(?:0[0-5]|[68])/, + Maestro: /^(?:5[0678]\d\d|6304|6390|67\d\d)/, + UnionPay: /^62/ + }; + for (const [type, regex] of Object.entries(patterns)) { + if (regex.test(cardNumber)) { + return type; + } + } + return "Unknown"; +} + +/** + * 计算 Luhn 校验位 (模10算法) + * 用于确保生成的卡号在数学上是有效的 + */ +function calculateLuhnCheckDigit(digits) { + let sum = 0; + let shouldDouble = true; + for (let i = digits.length - 1; i >= 0; i--) { + let digit = parseInt(digits[i]); + if (shouldDouble) { + digit *= 2; + if (digit > 9) { + digit -= 9; + } + } + sum += digit; + shouldDouble = !shouldDouble; + } + return (10 - sum % 10) % 10; +} + +/** + * 验证卡号是否符合 Luhn 算法 + */ +function validateLuhn(number) { + const digits = number.replace(/\D/g, ""); + let sum = 0; + let shouldDouble = false; + for (let i = digits.length - 1; i >= 0; i--) { + let digit = parseInt(digits[i]); + if (shouldDouble) { + digit *= 2; + if (digit > 9) { + digit -= 9; + } + } + sum += digit; + shouldDouble = !shouldDouble; + } + return sum % 10 === 0; +} + +/** + * 生成符合校验规则的卡号 + * @param {string} binPattern - BIN 格式 (如 "552461xxxxxxxxxx") + */ +function generateValidCardNumber(binPattern) { + let rawNumber = ""; + // 1. 填充随机数 + for (let i = 0; i < binPattern.length - 1; i++) { + if (binPattern[i] === "x" || binPattern[i] === "X") { + rawNumber += Math.floor(Math.random() * 10); + } else { + rawNumber += binPattern[i]; + } + } + // 2. 计算最后一位校验码 + const checkDigit = calculateLuhnCheckDigit(rawNumber); + rawNumber += checkDigit; + return rawNumber; +} + +function generateExpiryDate() { + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + + // 随机生成 1 到 60 个月后的日期 + const randomMonths = Math.floor(Math.random() * 60) + 1; + let targetMonth = currentMonth + randomMonths; + let targetYear = currentYear; + + while (targetMonth > 12) { + targetMonth -= 12; + targetYear += 1; + } + + return { + month: targetMonth.toString().padStart(2, "0"), + year: targetYear.toString() + }; +} + +function generateCVV(length = 3) { + let cvv = ""; + for (let i = 0; i < length; i++) { + cvv += Math.floor(Math.random() * 10); + } + return cvv; +} + +// ========================================== +// 核心生成逻辑 +// ========================================== + +/** + * 本地生成模式:使用内置算法生成卡号 + */ +function generateCardsLocally(bin, amount = 10) { + const cards = []; + const uniqueSet = new Set(); + console.log(`🎲 Generating ${amount} valid cards from BIN: ${bin}`); + + let attempts = 0; + const maxAttempts = amount * 10; + + while (cards.length < amount && attempts < maxAttempts) { + attempts++; + const number = generateValidCardNumber(bin); + + if (uniqueSet.has(number)) continue; + if (!validateLuhn(number)) { + console.warn("⚠️ Generated invalid card (should not happen):", number); + continue; + } + + uniqueSet.add(number); + const expiry = generateExpiryDate(); + const cvv = generateCVV(3); + const type = getCardType(number); + + cards.push({ + serial_number: cards.length + 1, + card_number: number, + expiry_month: expiry.month, + expiry_year: expiry.year, + cvv: cvv, + card_type: type, + full_format: `${number}|${expiry.month}|${expiry.year}|${cvv}`, + luhn_valid: true + }); + } + + console.log(`[cardbingenerator] Successfully generated ${cards.length} valid cards`); + + // 最后的完整性检查 + const invalid = cards.filter(c => !validateLuhn(c.card_number)); + if (invalid.length > 0) { + console.error(`❌ Found ${invalid.length} invalid cards!`); + } else { + console.log("[cardbingenerator] All cards passed Luhn validation"); + } + + // 统计卡种分布 + const stats = {}; + cards.forEach(c => { + stats[c.card_type] = (stats[c.card_type] || 0) + 1; + }); + console.log("📊 Card types:", stats); + + return cards; +} + +/** + * 简单生成模式:仅生成随机数,不进行 Luhn 校验 (用于某些不需要校验的测试场景) + */ +function generateCardsSimple(bin, amount = 10) { + const cards = []; + const uniqueSet = new Set(); + console.log(`🎲 Generating ${amount} cards (no validation) from BIN: ${bin}`); + + for (let i = 0; i < amount; i++) { + let number = ""; + for (let j = 0; j < bin.length; j++) { + if (bin[j] === "x" || bin[j] === "X") { + number += Math.floor(Math.random() * 10); + } else { + number += bin[j]; + } + } + + if (uniqueSet.has(number)) { + i--; + continue; + } + uniqueSet.add(number); + const expiry = generateExpiryDate(); + const cvv = generateCVV(3); + const type = getCardType(number); + + cards.push({ + serial_number: i + 1, + card_number: number, + expiry_month: expiry.month, + expiry_year: expiry.year, + cvv: cvv, + card_type: type, + full_format: `${number}|${expiry.month}|${expiry.year}|${cvv}`, + luhn_valid: false + }); + } + console.log(`[cardbingenerator] Generated ${cards.length} cards (simple mode)`); + return cards; +} + +// ========================================== +// 远程生成逻辑 (AKR-Gen) +// ========================================== + +/** + * 远程生成模式:操控第三方网站 (akr-gen.bigfk.com) 生成卡号 + * 当本地算法不满足需求时使用 + */ +async function generateCardsFromAKR(bin, onComplete) { + let tab = null; + try { + console.log("[cardbingenerator] Opening AKR-gen tab..."); + // 创建一个不激活的标签页(后台静默打开) + tab = await chrome.tabs.create({ + url: "https://akr-gen.bigfk.com/", + active: false + }); + + console.log("[cardbingenerator] Waiting for page load..."); + await new Promise(resolve => setTimeout(resolve, 4000)); + + console.log("[cardbingenerator] Filling BIN and generating cards..."); + // 注入脚本:自动填入BIN并点击生成 + const fillResult = await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + func: remotePage_fillAndClick, + args: [bin] + }); + console.log("Fill result:", fillResult[0]?.result); + + console.log("⏳ Waiting a moment before checking results..."); + await new Promise(resolve => setTimeout(resolve, 2000)); + + console.log("📥 Getting generated cards (will wait up to 10 seconds)..."); + // 注入脚本:抓取结果 + const scrapeResult = await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + func: remotePage_scrapeResults + }); + + console.log("[cardbingenerator] Closing AKR-gen tab..."); + await chrome.tabs.remove(tab.id); + tab = null; + + if (scrapeResult && scrapeResult[0] && scrapeResult[0].result) { + const parsedCards = parseScrapedCards(scrapeResult[0].result); + console.log(`[cardbingenerator] Generated ${parsedCards.length} cards`); + + if (parsedCards.length > 0) { + const randomAddr = await getRandomAddress(); + // 存入 storage 供 Content Script 使用 + chrome.storage.local.set({ + generatedCards: parsedCards, + randomData: randomAddr + }); + onComplete({ success: true, cards: parsedCards }); + } else { + console.error("❌ No cards generated from AKR"); + onComplete({ success: false, error: "No cards generated from AKR-gen" }); + } + } else { + console.error("❌ Failed to retrieve cards from result"); + onComplete({ success: false, error: "Failed to retrieve cards from page" }); + } + + } catch (err) { + console.error("❌ Error in generateCardsFromAKR:", err); + if (tab) { + try { await chrome.tabs.remove(tab.id); } catch (e) {} + } + onComplete({ success: false, error: err.message }); + } +} + +// -- 以下函数会被注入到目标页面执行,不能使用外部变量 -- + +function remotePage_fillAndClick(bin) { + return new Promise(resolve => { + function waitForElement(selector, retries = 10, delay = 300) { + return new Promise(res => { + let count = 0; + const check = () => { + const el = document.querySelector(selector) || document.getElementById(selector.replace("#", "")); + if (el) { + res(el); + } else if (count < retries) { + count++; + setTimeout(check, delay); + } else { + res(null); + } + }; + check(); + }); + } + + waitForElement("bin").then(input => { + if (input) { + console.log("[cardbingenerator] Found BIN input, filling with:", bin); + input.value = bin; + input.dispatchEvent(new Event("input", { bubbles: true })); + input.dispatchEvent(new Event("change", { bubbles: true })); + + setTimeout(() => { + waitForElement("button[type=\"submit\"]").then(btn => { + if (btn) { + console.log("[cardbingenerator] Found generate button, clicking..."); + btn.click(); + resolve(true); + } else { + console.error("❌ Generate button not found"); + resolve(false); + } + }); + }, 500); + } else { + console.error("❌ BIN input not found"); + resolve(false); + } + }); + }); +} + +function remotePage_scrapeResults() { + return new Promise(resolve => { + function waitLoop(retries = 20, delay = 500) { + let count = 0; + const check = () => { + const resultArea = document.getElementById("result"); + if (resultArea && resultArea.value.trim()) { + console.log("[cardbingenerator] Found generated cards:", resultArea.value.split("\n").length, "lines"); + resolve(resultArea.value); + } else if (count < retries) { + count++; + console.log(`[cardbingenerator] Waiting for cards... attempt ${count}/${retries}`); + setTimeout(check, delay); + } else { + console.error("❌ Timeout waiting for cards"); + resolve(""); + } + }; + check(); + } + waitLoop(); + }); +} + +// ---------------------------------------------------- + +function parseScrapedCards(text) { + if (!text) return []; + const lines = text.trim().split("\n"); + const cards = []; + + lines.forEach((line, index) => { + if (line.trim()) { + const parts = line.trim().split("|"); + if (parts.length === 4) { + cards.push({ + serial_number: index + 1, + card_number: parts[0], + expiry_month: parts[1], + expiry_year: parts[2], + cvv: parts[3], + full_format: line.trim() + }); + } + } + }); + return cards; +} + +/** + * 辅助:获取随机地址 + */ +async function getRandomAddress() { + return new Promise(resolve => { + chrome.storage.local.get(["customAddresses"], data => { + const custom = data.customAddresses || []; + const pool = [...custom, ...DEFAULT_ADDRESSES]; + if (pool.length === 0) { + resolve(DEFAULT_ADDRESSES[0]); + } else { + const selected = randomChoice(pool); + resolve(selected); + } + }); + }); +} + +// ========================================== +// 数据清理逻辑 (Browsing Data API) +// ========================================== + +async function clearStripeBrowsingData(sendResponse) { + try { + const domains = ["stripe.com", "checkout.stripe.com", "js.stripe.com", "hooks.stripe.com"]; + + // 1. 深度清理 Cookies (针对特定域) + for (const domain of domains) { + const cookies = await chrome.cookies.getAll({ domain: domain }); + for (const cookie of cookies) { + await chrome.cookies.remove({ + url: "https://" + cookie.domain + cookie.path, + name: cookie.name + }); + } + } + + // 2. 清理浏览器缓存和存储 (针对特定 Origin) + await chrome.browsingData.remove({ + origins: domains.map(d => "https://" + d) + }, { + cache: true, + cookies: true, + localStorage: true, + indexedDB: true, + serviceWorkers: true, + cacheStorage: true + }); + + console.log("[cardbingenerator] Deep clear completed for Stripe domains"); + if (sendResponse) sendResponse({ success: true }); + + } catch (err) { + console.error("Error in deep clear:", err); + if (sendResponse) sendResponse({ success: false, error: err.message }); + } +} + +// ========================================== +// 消息监听与路由 +// ========================================== + +// 安装时初始化默认 BIN +chrome.runtime.onInstalled.addListener(() => { + chrome.storage.local.get(["currentBin", "binHistory"], data => { + if (!data.currentBin) { + chrome.storage.local.set({ + currentBin: "552461xxxxxxxxxx", + binHistory: ["552461xxxxxxxxxx"] + }); + } + }); +}); + +// 主消息处理器 +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "generateCards") { + // 路由到生成逻辑 + generateCardsHandler(request.bin, request.useValidation, sendResponse); + return true; // 保持消息通道打开以进行异步响应 + } + if (request.action === "clearBrowsingData") { + // 路由到清理逻辑 + clearStripeBrowsingData(sendResponse); + return true; + } +}); + +async function generateCardsHandler(bin, useValidation = true, sendResponse) { + try { + console.log(`[cardbingenerator] Starting card generation... (Luhn: ${useValidation ? "ON" : "OFF"})`); + + // 根据配置决定是验证 Luhn 还是简单生成 + const cards = useValidation ? + generateCardsLocally(bin, 10) : + generateCardsSimple(bin, 10); + + if (cards.length > 0) { + const address = await getRandomAddress(); + + // 将结果存入 storage,这样 Content Script (第一部分代码) 就能读到了 + chrome.storage.local.set({ + generatedCards: cards, + randomData: address + }); + + console.log(`[cardbingenerator] Generated and saved ${cards.length} cards`); + sendResponse({ success: true, cards: cards }); + } else { + console.error("❌ No cards generated"); + sendResponse({ success: false, error: "Failed to generate cards" }); + } + } catch (err) { + console.error("❌ Error in generateCardsHandler:", err); + sendResponse({ success: false, error: err.message }); + } +} diff --git a/content.js b/content.js new file mode 100644 index 0000000..029e53a --- /dev/null +++ b/content.js @@ -0,0 +1,880 @@ +/** + * 全局状态标记 + */ +let isProcessing = false; +let fillButton = null; +let clearButton = null; + +/** + * 默认地址数据源 + */ +const DEFAULT_ADDRESSES = [{ + name: "John Smith", + firstName: "John", + lastName: "Smith", + address1: "69 Adams Street", + address2: "", + city: "Brooklyn", + state: "New York", + stateCode: "NY", + postal: "11201", + countryText: "United States", + countryValue: "US" +}, { + name: "Michael Johnson", + firstName: "Michael", + lastName: "Johnson", + address1: "3511 Carlisle Avenue", + address2: "", + city: "Covington", + state: "Kentucky", + stateCode: "KY", + postal: "41015", + countryText: "United States", + countryValue: "US" +}]; + +/** + * 字段匹配同义词词典 + * 用于启发式识别表单字段类型 + */ +const FIELD_SYNONYMS = { + fullName: ["full name", "name", "cardholder name", "card name", "cc-name"], + firstName: ["first name", "given-name"], + lastName: ["last name", "family-name", "surname"], + address1: ["address", "address line 1", "street", "addressline1", "address-line1", "address-line-1"], + address2: ["address line 2", "apt", "apartment", "addressline2", "address-line2", "suite"], + city: ["city", "locality", "address-level2"], + state: ["state", "region", "province", "administrative area", "address-level1", "address level 1"], + postal: ["postal", "zip", "postcode", "postal-code"], + country: ["country", "country or region"] +}; + +const CARD_FIELD_WORDS = ["card", "cvc", "cvv", "expiry", "expiration", "valid thru", "month", "year"]; + +// ========================================== +// 核心工具函数 +// ========================================== + +/** + * 获取随机地址 + * 优先从 storage 读取配置,支持 static/manual/auto 模式 + */ +async function getRandomAddress() { + return new Promise(resolve => { + chrome.storage.local.get(["customAddresses", "addressSource"], data => { + const customList = data.customAddresses || []; + const sourceMode = data.addressSource || "static"; + let addressPool = []; + + switch (sourceMode) { + case "static": + addressPool = DEFAULT_ADDRESSES; + break; + case "manual": + addressPool = customList.length > 0 ? customList : DEFAULT_ADDRESSES; + break; + case "auto": + addressPool = DEFAULT_ADDRESSES; + break; + default: + addressPool = DEFAULT_ADDRESSES; + } + + if (addressPool.length === 0) { + resolve(DEFAULT_ADDRESSES[0]); + } else { + const selected = addressPool[Math.floor(Math.random() * addressPool.length)]; + console.log(`[cardbingenerator] Using ${sourceMode} address:`, selected.name); + resolve(selected); + } + }); + }); +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function randomDelay(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +/** + * 模拟人类输入文本 + * @param {HTMLElement} element - 目标输入框 + * @param {string} text - 要输入的文本 + * @param {boolean} clearFirst - 是否先清空输入框 (默认 false) + */ +async function typeText(element, text, clearFirst = false) { + if (!element || !text) return; + + element.focus(); + element.scrollIntoView({ behavior: "smooth", block: "center" }); + await sleep(randomDelay(150, 300)); + + if (clearFirst && text.length < 50) { + // 模拟逐字删除或清空 + element.value = ""; + for (let i = 0; i < text.length; i++) { + element.value += text[i]; + element.dispatchEvent(new Event("input", { bubbles: true })); + await sleep(randomDelay(30, 80)); // 模拟击键间隔 + } + element.dispatchEvent(new Event("change", { bubbles: true })); + } else { + // 直接赋值模式 + element.value = text; + element.dispatchEvent(new Event("input", { bubbles: true })); + element.dispatchEvent(new Event("change", { bubbles: true })); + } + + await sleep(randomDelay(100, 200)); + element.blur(); + await sleep(randomDelay(200, 400)); +} + +/** + * 收集页面所有根节点(包括 Shadow DOM) + * 用于穿透 Shadow DOM 查找元素 + */ +function collectRoots() { + const roots = [document]; + const queue = [document.documentElement]; + + while (queue.length) { + const node = queue.pop(); + if (!node) continue; + + if (node.shadowRoot) { + roots.push(node.shadowRoot); + } + + const children = node.children || []; + for (let i = 0; i < children.length; i++) { + queue.push(children[i]); + } + } + return roots; +} + +/** + * 检测元素是否可见且可交互 + */ +function isVisible(el) { + if (!el) return false; + const rect = el.getBoundingClientRect(); + const style = window.getComputedStyle(el); + + if (style.visibility === "hidden" || style.display === "none") return false; + if (el.disabled) return false; + if (rect.width <= 0 || rect.height <= 0) return false; + if (el.type === "hidden") return false; + + return true; +} + +/** + * 收集页面所有可见的表单元素 (input, select, textarea) + */ +function collectFormElements() { + const elements = []; + for (const root of collectRoots()) { + const nodes = root.querySelectorAll("input, select, textarea"); + nodes.forEach(node => { + if (isVisible(node)) { + elements.push(node); + } + }); + } + return elements; +} + +/** + * 使用原生 Setter 设置值并触发事件 + * 绕过 React/Vue 等框架的状态绑定限制 + */ +async function setNativeValueAndDispatch(element, value, simulateTyping = false) { + if (!element) return; + + try { + element.focus(); + element.scrollIntoView({ behavior: "smooth", block: "center" }); + await sleep(randomDelay(150, 300)); + + const tagName = element.tagName; + + if (tagName === "INPUT" || tagName === "TEXTAREA") { + const proto = tagName === "INPUT" ? window.HTMLInputElement.prototype : window.HTMLTextAreaElement.prototype; + const nativeSetter = Object.getOwnPropertyDescriptor(proto, "value").set; + + if (simulateTyping && value && value.length < 30) { + // 模拟打字效果 + element.value = ""; + for (let i = 0; i < value.length; i++) { + nativeSetter.call(element, element.value + value[i]); + element.dispatchEvent(new Event("input", { bubbles: true })); + await sleep(randomDelay(50, 120)); + } + } else { + // 快速填充 + nativeSetter.call(element, value); + element.dispatchEvent(new Event("input", { bubbles: true })); + } + + element.dispatchEvent(new Event("change", { bubbles: true })); + await sleep(randomDelay(150, 250)); + element.blur(); + + } else if (tagName === "SELECT") { + element.value = value; + element.dispatchEvent(new Event("change", { bubbles: true })); + await sleep(randomDelay(200, 350)); + element.blur(); + } + + await sleep(randomDelay(300, 500)); + + } catch (err) { + // 降级处理:直接赋值 + try { + element.focus(); + await sleep(randomDelay(150, 300)); + element.value = value; + element.dispatchEvent(new Event("input", { bubbles: true })); + element.dispatchEvent(new Event("change", { bubbles: true })); + await sleep(randomDelay(150, 250)); + element.blur(); + await sleep(randomDelay(300, 500)); + } catch (ignored) {} + } +} + +/** + * 处理 Select 下拉框的值选择 + * 尝试匹配 value, textContent 或模糊匹配 + */ +function pickSelectValue(selectEl, valueOptions, textOptions) { + if (!selectEl || selectEl.tagName !== "SELECT") return null; + + const options = Array.from(selectEl.options || []); + const normalize = str => (str || "").toLowerCase().replace(/\s+/g, " ").trim(); + const includesText = (text, query) => normalize(text).includes(normalize(query)); + + // 1. 尝试匹配 value + for (const val of valueOptions || []) { + const found = options.find(opt => normalize(opt.value) === normalize(val)); + if (found) return found.value; + } + + // 2. 尝试匹配 textContent (精确) + for (const txt of textOptions || []) { + const found = options.find(opt => normalize(opt.textContent) === normalize(txt)); + if (found) return found.value; + } + + // 3. 尝试匹配 textContent (包含) + for (const txt of textOptions || []) { + const found = options.find(opt => includesText(opt.textContent, txt)); + if (found) return found.value; + } + + return null; +} + +// ========================================== +// 字段识别逻辑 +// ========================================== + +function getElementTextAttributes(el) { + const attrs = [ + el.getAttribute("name"), + el.getAttribute("id"), + el.getAttribute("placeholder"), + el.getAttribute("aria-label"), + el.getAttribute("autocomplete"), + el.getAttribute("data-testid"), + el.getAttribute("data-qa") + ].filter(Boolean); + return attrs.join(" ").toLowerCase(); +} + +function matchesAny(text, keywords) { + const lowerText = text.toLowerCase(); + return keywords.some(kw => lowerText.includes(kw.toLowerCase())); +} + +function isCardField(el) { + const text = getElementTextAttributes(el); + if (!text) return false; + return matchesAny(text, CARD_FIELD_WORDS); +} + +/** + * 计算字段与特定类型的匹配分数 + */ +function scoreForSynonyms(el, synonyms) { + const text = getElementTextAttributes(el); + let score = 0; + if (!text) return score; + + const autocomplete = (el.getAttribute("autocomplete") || "").toLowerCase(); + + // 权重最高:autocomplete 属性匹配 + for (const syn of synonyms) { + if (autocomplete.split(/\s+/).includes(syn.toLowerCase())) { + score += 6; + } + } + + // 权重中等:name 或 id 包含关键词 + const nameId = [(el.getAttribute("name") || "").toLowerCase(), (el.getAttribute("id") || "").toLowerCase()].join(" "); + for (const syn of synonyms) { + if (nameId.includes(syn.toLowerCase())) { + score += 4; + } + } + + // 权重最低:placeholder 或 aria-label 包含关键词 + const desc = [(el.getAttribute("placeholder") || "").toLowerCase(), (el.getAttribute("aria-label") || "").toLowerCase()].join(" "); + for (const syn of synonyms) { + if (desc.includes(syn.toLowerCase())) { + score += 2; + } + } + + return score; +} + +/** + * 寻找最佳匹配字段 + */ +function findBestField(elements, synonyms, validator) { + let bestEl = null; + let bestScore = 0; + + for (const el of elements) { + if (validator && !validator(el)) continue; + + const score = scoreForSynonyms(el, synonyms); + if (score > bestScore) { + bestEl = el; + bestScore = score; + } + } + return bestEl; +} + +/** + * 识别所有非卡号类的普通表单字段 (姓名、地址等) + */ +function detectAddressFields() { + const formElements = collectFormElements().filter(el => !isCardField(el)); + const fields = {}; + + fields.firstName = findBestField(formElements, FIELD_SYNONYMS.firstName, el => el.tagName !== "SELECT"); + fields.lastName = findBestField(formElements, FIELD_SYNONYMS.lastName, el => el.tagName !== "SELECT"); + fields.fullName = findBestField(formElements, FIELD_SYNONYMS.fullName, el => el.tagName !== "SELECT"); + fields.address1 = findBestField(formElements, FIELD_SYNONYMS.address1, el => el.tagName !== "SELECT"); + fields.address2 = findBestField(formElements, FIELD_SYNONYMS.address2, el => el.tagName !== "SELECT"); + fields.city = findBestField(formElements, FIELD_SYNONYMS.city, el => el.tagName !== "SELECT"); + fields.state = findBestField(formElements, FIELD_SYNONYMS.state, el => el.tagName !== "SELECT"); + fields.postal = findBestField(formElements, FIELD_SYNONYMS.postal, el => el.tagName !== "SELECT"); + fields.country = findBestField(formElements, FIELD_SYNONYMS.country, () => true); // Country 可以是 select + + // 逻辑修正:避免全名和名/姓重复匹配 + if (fields.fullName) { + if (fields.firstName === fields.fullName) fields.firstName = null; + if (fields.lastName === fields.fullName) fields.lastName = null; + } + if (fields.firstName && fields.lastName && fields.firstName === fields.lastName) { + fields.fullName = fields.firstName; + fields.firstName = null; + fields.lastName = null; + } + + return fields; +} + +/** + * 专门识别信用卡相关字段 + * 使用 CSS 选择器优先匹配 + */ +function detectCardFields() { + const roots = collectRoots(); + let numEl, expEl, cvcEl; + + const numSelectors = ["input[autocomplete=\"cc-number\"]", "input[name*=\"cardnumber\" i]", "input[id*=\"cardnumber\" i]", "input[name=\"cardNumber\"]", "input[placeholder*=\"1234\"]", "#cardNumber"]; + const expSelectors = ["input[autocomplete=\"cc-exp\"]", "input[name*=\"exp\" i]", "input[id*=\"exp\" i]", "input[placeholder*=\"MM\"]", "input[name=\"cardExpiry\"]", "#cardExpiry"]; + const cvcSelectors = ["input[autocomplete=\"cc-csc\"]", "input[name*=\"cvc\" i]", "input[name*=\"cvv\" i]", "input[id*=\"cvc\" i]", "input[placeholder*=\"CVC\"]", "input[name=\"cardCvc\"]", "#cardCvc"]; + + function findInRoots(selectors) { + for (const root of roots) { + for (const sel of selectors) { + const el = root.querySelector(sel); + if (el && isVisible(el)) return el; + } + } + return null; + } + + numEl = findInRoots(numSelectors); + expEl = findInRoots(expSelectors); + cvcEl = findInRoots(cvcSelectors); + + if (numEl || expEl || cvcEl) { + return { number: numEl, exp: expEl, cvc: cvcEl }; + } + return null; +} + +/** + * 自动点击“手动输入地址”按钮(如果有的话) + * 常见于 Google Places Autocomplete 覆盖了原生输入框的情况 + */ +function clickManualAddressIfPresent() { + const keywords = ["enter address manually", "manually enter address", "ввести адрес вручную", "введите адрес вручную", "адрес вручную"]; + try { + const roots = collectRoots(); + const tagSelectors = ["button", "[role=\"button\"]", "a", ".Button", ".Link", "span[role=\"button\"]", "div[role=\"button\"]"]; + + for (const root of roots) { + for (const selector of tagSelectors) { + const elements = root.querySelectorAll(selector); + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + if (!isVisible(el)) continue; + + const text = [ + el.textContent || "", + el.getAttribute("aria-label") || "", + el.getAttribute("title") || "", + el.getAttribute("data-testid") || "" + ].join(" ").toLowerCase(); + + if (!text) continue; + + if (keywords.some(kw => text.includes(kw.toLowerCase()))) { + const clickable = el.closest("button, [role=\"button\"], a, [role=\"link\"]") || el; + console.log("[cardbingenerator] Clicking manual address button:", el.textContent); + clickable.click(); + return true; + } + } + } + } + } catch (err) {} + return false; +} + +// ========================================== +// 主业务逻辑:自动填充流程 +// ========================================== + +async function autofillAll() { + if (isProcessing) return; + isProcessing = true; + + try { + showNotification("🔄 Starting auto-fill...", "info"); + await sleep(randomDelay(500, 1000)); + + // 1. 生成新卡片 + showNotification("🔄 Generating fresh cards...", "info"); + const storage = await chrome.storage.local.get(["currentBin"]); + const currentBin = storage.currentBin || "552461xxxxxxxxxx"; + + // 发送消息给 background script 生成卡片 + const genResult = await new Promise(resolve => { + chrome.runtime.sendMessage({ + action: "generateCards", + bin: currentBin, + stripeTabId: null + }, response => resolve(response)); + }); + + if (!genResult || !genResult.success) { + showNotification("❌ Failed to generate cards: " + (genResult?.error || "Unknown error"), "error"); + isProcessing = false; + return; + } + + await sleep(2000); + + // 2. 获取生成的卡片数据 + const cardStorage = await chrome.storage.local.get(["generatedCards"]); + if (!cardStorage.generatedCards || cardStorage.generatedCards.length === 0) { + showNotification("❌ No cards were generated", "error"); + isProcessing = false; + return; + } + + const selectedCard = cardStorage.generatedCards[Math.floor(Math.random() * cardStorage.generatedCards.length)]; + const addressData = await getRandomAddress(); + + showNotification("💳 Filling card details...", "info"); + await sleep(randomDelay(400, 700)); + + // 3. 处理 Stripe 常见的折叠卡片按钮 + const accordionBtn = document.querySelector("[data-testid=\"card-accordion-item-button\"]"); + if (accordionBtn && isVisible(accordionBtn)) { + console.log("[cardbingenerator] Clicking card button..."); + accordionBtn.scrollIntoView({ behavior: "smooth", block: "center" }); + await sleep(randomDelay(300, 500)); + accordionBtn.click(); + await sleep(randomDelay(800, 1200)); + } + + // 4. 填充卡片信息 + const cardFields = detectCardFields(); + if (cardFields) { + if (cardFields.number) { + console.log("[cardbingenerator] Filling card number..."); + await setNativeValueAndDispatch(cardFields.number, selectedCard.card_number); + } + if (cardFields.exp) { + console.log("[cardbingenerator] Filling expiry date..."); + const expStr = selectedCard.expiry_month + " / " + selectedCard.expiry_year.slice(-2); + await setNativeValueAndDispatch(cardFields.exp, expStr); + } + if (cardFields.cvc) { + console.log("[cardbingenerator] Filling CVC..."); + await setNativeValueAndDispatch(cardFields.cvc, selectedCard.cvv); + } + } + + // 5. 填充地址信息 + showNotification("📝 Filling address...", "info"); + const addrFields = detectAddressFields(); + + // 优先处理国家选择,因为这可能会刷新表单格式 + if (addrFields.country && addrFields.country.tagName === "SELECT") { + console.log("[cardbingenerator] Filling country..."); + const countryVal = pickSelectValue(addrFields.country, [addressData.countryValue], [addressData.countryText]); + if (countryVal) { + await setNativeValueAndDispatch(addrFields.country, countryVal); + } + await sleep(300); + } + + // 检查是否有“手动输入地址”按钮并点击 + const clickedManual = clickManualAddressIfPresent(); + if (clickedManual) { + console.log("[cardbingenerator] Manual address button clicked"); + await sleep(randomDelay(800, 1200)); + } + + // 填充详细地址字段 + const fillAddressDetails = async () => { + const currentFields = detectAddressFields(); // 重新检测,因为 DOM 可能已变化 + + // 名字处理逻辑 + if (currentFields.firstName && currentFields.lastName && currentFields.firstName !== currentFields.lastName) { + console.log("[cardbingenerator] Filling first name..."); + await setNativeValueAndDispatch(currentFields.firstName, addressData.firstName, true); + console.log("[cardbingenerator] Filling last name..."); + await setNativeValueAndDispatch(currentFields.lastName, addressData.lastName, true); + } else { + const nameField = currentFields.fullName || currentFields.firstName || currentFields.lastName; + if (nameField) { + console.log("[cardbingenerator] Filling full name..."); + await setNativeValueAndDispatch(nameField, addressData.name, true); + } + } + + if (currentFields.address1) { + console.log("[cardbingenerator] Filling address line 1..."); + await setNativeValueAndDispatch(currentFields.address1, addressData.address1, false); + } + if (currentFields.address2) { + console.log("[cardbingenerator] Filling address line 2..."); + await setNativeValueAndDispatch(currentFields.address2, addressData.address2, false); + } + if (currentFields.city) { + console.log("[cardbingenerator] Filling city..."); + await setNativeValueAndDispatch(currentFields.city, addressData.city, true); + } + if (currentFields.postal) { + console.log("[cardbingenerator] Filling postal code..."); + await setNativeValueAndDispatch(currentFields.postal, addressData.postal, false); + } + }; + + await fillAddressDetails(); + + // 延迟填充 State/Province,因为有些表单需要先填 Country/Zip 才会出现 State + const fillState = async () => { + await sleep(randomDelay(400, 600)); + const fieldsNow = detectAddressFields(); + if (!fieldsNow.state) return; + + if (fieldsNow.state.tagName === "SELECT") { + console.log("📍 Filling state (select)..."); + const stateVal = pickSelectValue(fieldsNow.state, [addressData.stateCode], [addressData.state]); + if (stateVal) { + await setNativeValueAndDispatch(fieldsNow.state, stateVal); + } + } else { + console.log("📍 Filling state (input)..."); + await setNativeValueAndDispatch(fieldsNow.state, addressData.stateCode || addressData.state, true); + } + }; + + await fillState(); + await sleep(randomDelay(600, 1000)); + + // 清理已使用的卡片数据 + chrome.storage.local.remove(["generatedCards"], () => { + console.log("[cardbingenerator] Cleared used cards from storage"); + }); + + console.log("[cardbingenerator] Auto-fill completed!"); + showNotification("✅ All fields filled successfully!", "success"); + + } catch (err) { + chrome.storage.local.remove(["generatedCards"]); + showNotification("❌ Error: " + err.message, "error"); + console.error("Autofill error:", err); + } + isProcessing = false; +} + +// ========================================== +// UI 辅助功能:通知和清理按钮 +// ========================================== + +function showNotification(message, type = "info") { + const existing = document.getElementById("auto-card-filler-notification"); + if (existing) existing.remove(); + + const notif = document.createElement("div"); + notif.id = "auto-card-filler-notification"; + notif.textContent = message; + + const colors = { + info: "#3498db", + success: "#2ecc71", + warning: "#f39c12", + error: "#e74c3c" + }; + + Object.assign(notif.style, { + position: "fixed", + top: "20px", + right: "20px", + background: colors[type] || colors.info, + color: "white", + padding: "15px 20px", + borderRadius: "8px", + boxShadow: "0 4px 12px rgba(0,0,0,0.3)", + zIndex: "9999999", + fontSize: "14px", + fontWeight: "600", + maxWidth: "300px", + animation: "slideIn 0.3s ease-out" + }); + + const styleEl = document.createElement("style"); + styleEl.textContent = ` + @keyframes slideIn { + from { transform: translateX(400px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + `; + + if (!document.getElementById("autofill-notification-style")) { + styleEl.id = "autofill-notification-style"; + document.head.appendChild(styleEl); + } + + document.body.appendChild(notif); + + setTimeout(() => { + notif.style.transition = "all 0.3s ease-out"; + notif.style.transform = "translateX(400px)"; + notif.style.opacity = "0"; + setTimeout(() => notif.remove(), 300); + }, 5000); +} + +function findPaymentMethodHeader() { + const roots = collectRoots(); + for (const root of roots) { + const headers = root.querySelectorAll("h1, h2, h3, h4, .Header, [class*=\"header\"], [class*=\"title\"], [class*=\"Title\"]"); + for (const h of headers) { + const text = h.textContent.toLowerCase().trim(); + if (["payment method", "payment", "метод оплаты", "способ оплаты"].includes(text) || text.includes("payment method")) { + return h; + } + } + } + return null; +} + +function createFillButton() { + if (clearButton || document.getElementById("stripe-clear-btn")) return; + + const header = findPaymentMethodHeader(); + clearButton = document.createElement("button"); + clearButton.id = "stripe-clear-btn"; + clearButton.innerHTML = "🗑️ Clear All Data"; + + // 注入按钮样式 + clearButton.style.cssText = ` + background: #dc3545; + color: white; + border: none; + border-radius: 8px; + padding: 8px 16px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3); + transition: all 0.2s ease; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + margin-left: 8px; + vertical-align: middle; + white-space: nowrap; + `; + + clearButton.addEventListener("mouseenter", () => { + clearButton.style.transform = "translateY(-1px)"; + clearButton.style.boxShadow = "0 4px 12px rgba(220, 53, 69, 0.4)"; + }); + + clearButton.addEventListener("mouseleave", () => { + clearButton.style.transform = "translateY(0)"; + clearButton.style.boxShadow = "0 2px 8px rgba(220, 53, 69, 0.3)"; + }); + + clearButton.addEventListener("click", async () => { + if (confirm(`⚠️ Clear all Stripe data? This will:\n\n• Delete all cookies\n• Clear localStorage\n• Clear sessionStorage\n• Clear cache\n• Reload the page\n\nContinue?`)) { + clearButton.disabled = true; + clearButton.innerHTML = "⏳ Clearing..."; + await clearAllStripeData(); + showNotification("✅ All data cleared! Reloading...", "success"); + setTimeout(() => { + location.reload(); + }, 1000); + } + }); + + // 尝试将按钮插入到支付标题旁边,如果找不到标题则悬浮显示 + if (header) { + if (header.parentElement) { + const container = document.createElement("div"); + container.style.cssText = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;"; + const headerClone = header.cloneNode(true); + headerClone.style.margin = "0"; + const btnGroup = document.createElement("div"); + btnGroup.style.cssText = "display: flex; gap: 8px;"; + btnGroup.appendChild(clearButton); + container.appendChild(headerClone); + container.appendChild(btnGroup); + header.parentElement.replaceChild(container, header); + } + } else { + clearButton.style.cssText += ` + position: fixed; + top: 20px; + right: 20px; + z-index: 999999; + padding: 12px 20px; + font-size: 14px; + `; + document.body.appendChild(clearButton); + } +} + +/** + * 彻底清理浏览器数据 (Stripe 反指纹追踪) + */ +async function clearAllStripeData() { + try { + localStorage.clear(); + sessionStorage.clear(); + + // 清理 IndexedDB + if (window.indexedDB) { + const dbs = await window.indexedDB.databases(); + for (const db of dbs) { + window.indexedDB.deleteDatabase(db.name); + } + } + + // 暴力清理 Cookies + const cookies = document.cookie.split(";"); + for (let cookie of cookies) { + const eqPos = cookie.indexOf("="); + const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/"; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=" + location.hostname; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=." + location.hostname; + } + + if ("caches" in window) { + const keys = await caches.keys(); + for (const key of keys) { + await caches.delete(key); + } + } + + chrome.runtime.sendMessage({ action: "clearBrowsingData" }); + console.log("[cardbingenerator] All Stripe data cleared"); + } catch (err) { + console.error("Error clearing data:", err); + showNotification("⚠️ Partial clear - some data may remain", "warning"); + } +} + +function shouldShowButton() { + const hasCardFields = detectCardFields(); + const hasAddrFields = detectAddressFields(); + return hasCardFields || hasAddrFields.fullName || hasAddrFields.address1; +} + +function initButton() { + if (shouldShowButton()) { + createFillButton(); + } +} + +// ========================================== +// 初始化与事件监听 +// ========================================== + +const observer = new MutationObserver(() => { + if (!fillButton && shouldShowButton()) { + createFillButton(); + } +}); + +if (document.body) { + observer.observe(document.body, { childList: true, subtree: true }); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initButton); +} else { + initButton(); +} + +// 多次尝试初始化,应对动态加载的 SPA +setTimeout(initButton, 1000); +setTimeout(initButton, 2000); +setTimeout(initButton, 3000); + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "fillForm") { + autofillAll(); + } +}); + +window.addEventListener("beforeunload", () => { + chrome.storage.local.remove(["generatedCards"]); + console.log("[cardbingenerator] Cleared cards on page unload"); +}); + +chrome.storage.local.remove(["generatedCards"], () => { + console.log("[cardbingenerator] Cleared old cards on page load"); +}); diff --git a/icon128.png b/icon128.png new file mode 100644 index 0000000..4b6dd90 Binary files /dev/null and b/icon128.png differ diff --git a/icon16.png b/icon16.png new file mode 100644 index 0000000..91428a5 Binary files /dev/null and b/icon16.png differ diff --git a/icon48.png b/icon48.png new file mode 100644 index 0000000..d433b67 Binary files /dev/null and b/icon48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f17deb4 --- /dev/null +++ b/manifest.json @@ -0,0 +1,43 @@ +{ + "manifest_version": 3, + "name": "cardbingenerator - Stripe Auto Fill", + "version": "1.0.0", + "description": "cardbingenerator", + "permissions": [ + "storage", + "activeTab", + "tabs", + "scripting", + "cookies", + "browsingData" + ], + "host_permissions": [ + "https://checkout.stripe.com/*", + "https://stripe.com/*", + "https://*.stripe.com/*" + ], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + } + }, + "content_scripts": [ + { + "matches": [ + "https://checkout.stripe.com/*", + "https://stripe.com/*", + "https://*.stripe.com/*" + ], + "js": ["content.js"], + "run_at": "document_idle", + "all_frames": true + } + ] +} + diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..fbcdb61 --- /dev/null +++ b/popup.html @@ -0,0 +1,249 @@ + + + + + + cardbingenerator + + + + + + +
+
+
+ +

cardbingenerator

+
+
+
Blackhat_bullet
+ +
+
+ +
+ + + +
+ +
+ +
+
+
+ 💳 +

Card Generator

+
+ +
+ +
+ + + +
+ + +
+
+ +
+ +
+ +
+
🏠 Address Source:
+ +
+ +
+
👤 Name Source:
+ +
Names are taken from addresses
+
+
+ + + Auto-fills with 'x' to 16 digits • Max 19 digits for long cards + +
+ + + +
+
+ +
+
+ 📜 +

History

+
+
+
+
+ + +
+
+
+ 💎 +

BIN Generator

+
+
+
💎
+
+ Powered by cardbingenerator +
+
+ Visit cardbingenerator.com for advanced BIN generation with multiple card networks, formats, and validation options. +
+ + Visit BIN Generator + +
+
+ +
+ + +
+
+ + +
+ + +
+
+ + + +
+ + +
+ + +
+ +
+
+
+
+ + +
+
+
+ + +
+ +
+ +
+
+
+
+
+ + + +
+
+ + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..9eeb6eb --- /dev/null +++ b/popup.js @@ -0,0 +1,627 @@ +/** + * 界面元素引用 (UI References) + */ +const tabs = document.querySelectorAll(".tab-btn"); +const tabContents = document.querySelectorAll(".tab-content"); +const binInput = document.getElementById("binInput"); +const addBinBtn = document.getElementById("addBinBtn"); +const generateCardsBtn = document.getElementById("generateCardsBtn"); +const statusMessage = document.getElementById("statusMessage"); +const binHistoryList = document.getElementById("binHistoryList"); + +// 地址管理相关元素 +const nameInput = document.getElementById("nameInput"); +const address1Input = document.getElementById("address1Input"); +const address2Input = document.getElementById("address2Input"); +const cityInput = document.getElementById("cityInput"); +const stateInput = document.getElementById("stateInput"); +const zipInput = document.getElementById("zipInput"); +const addAddressBtn = document.getElementById("addAddressBtn"); +const addressesList = document.getElementById("addressesList"); + +// 姓名管理相关元素 +const firstNameInput = document.getElementById("firstNameInput"); +const lastNameInput = document.getElementById("lastNameInput"); +const addNameBtn = document.getElementById("addNameBtn"); +const namesList = document.getElementById("namesList"); + +// 设置相关元素 +const useLuhnValidation = document.getElementById("useLuhnValidation"); +const DEFAULT_BIN = "552461xxxxxxxxxx"; + +/** + * 格式化输入:BIN 输入框 + * 自动转大写,移除非数字/X字符 + */ +binInput.addEventListener("input", (e) => { + let val = e.target.value.toUpperCase(); + val = val.replace(/[^0-9X]/g, ""); + if (val.length > 19) { + val = val.substring(0, 19); + } + e.target.value = val; +}); + +/** + * 格式化输入:粘贴处理 + * 自动补全 X,确保格式符合 16 位卡号长度 + */ +binInput.addEventListener("blur", (e) => { + let val = e.target.value.trim().replace(/[^0-9X]/g, ""); + // 如果长度不足且非空,自动补 X + if (val.length > 0 && val.length < 16) { + const currentLen = val.replace(/X/g, "").length; + const needed = 16 - currentLen; + val = val + "X".repeat(needed); + } + e.target.value = val; +}); + +// ========================================== +// 设置菜单逻辑 +// ========================================== +const settingsToggleBtn = document.getElementById("settingsToggleBtn"); +const miniSettings = document.getElementById("miniSettings"); + +if (settingsToggleBtn && miniSettings) { + settingsToggleBtn.addEventListener("click", (e) => { + e.stopPropagation(); + miniSettings.classList.toggle("show"); + settingsToggleBtn.classList.toggle("active"); + }); + + // 点击外部关闭菜单 + document.addEventListener("click", (e) => { + if (!miniSettings.contains(e.target) && e.target !== settingsToggleBtn && !settingsToggleBtn.contains(e.target)) { + miniSettings.classList.remove("show"); + settingsToggleBtn.classList.remove("active"); + } + }); +} + +// Luhn 校验开关 +if (useLuhnValidation) { + useLuhnValidation.addEventListener("change", () => { + chrome.storage.local.set({ + useLuhnValidation: useLuhnValidation.checked + }); + }); + // 初始化状态 + chrome.storage.local.get(["useLuhnValidation"], (data) => { + if (data.useLuhnValidation !== undefined) { + useLuhnValidation.checked = data.useLuhnValidation; + } + }); +} + +// 数据源选择器逻辑 +const addressSourceSelect = document.getElementById("addressSourceSelect"); +const nameSourceSelect = document.getElementById("nameSourceSelect"); + +if (addressSourceSelect) { + addressSourceSelect.addEventListener("change", () => { + const val = addressSourceSelect.value; + chrome.storage.local.set({ addressSource: val }); + console.log("✅ Address source changed to:", val); + }); + chrome.storage.local.get(["addressSource"], (data) => { + if (data.addressSource) { + addressSourceSelect.value = data.addressSource; + } else { + // 默认设置为 static + addressSourceSelect.value = "static"; + chrome.storage.local.set({ addressSource: "static" }); + } + }); +} + +if (nameSourceSelect) { + nameSourceSelect.addEventListener("change", () => { + const val = nameSourceSelect.value; + chrome.storage.local.set({ nameSource: val }); + console.log("✅ Name source changed to:", val); + }); + chrome.storage.local.get(["nameSource"], (data) => { + if (data.nameSource) { + nameSourceSelect.value = data.nameSource; + } else { + nameSourceSelect.value = "static"; + chrome.storage.local.set({ nameSource: "static" }); + } + }); +} + +// 初始化加载所有数据 +loadData(); + +// ========================================== +// 标签页切换逻辑 (Tabs) +// ========================================== +tabs.forEach(tab => { + tab.addEventListener("click", () => { + const targetId = tab.dataset.tab; + tabs.forEach(t => t.classList.remove("active")); + tabContents.forEach(c => c.classList.remove("active")); + + tab.classList.add("active"); + document.getElementById(targetId + "-tab").classList.add("active"); + }); +}); + +const subTabs = document.querySelectorAll(".sub-tab-btn"); +const subTabContents = document.querySelectorAll(".sub-tab-content"); + +subTabs.forEach(tab => { + tab.addEventListener("click", () => { + const targetId = tab.dataset.subtab; + subTabs.forEach(t => t.classList.remove("active")); + subTabContents.forEach(c => c.classList.remove("active")); + + tab.classList.add("active"); + document.getElementById(targetId + "-subtab").classList.add("active"); + }); +}); + +// ========================================== +// 核心功能:添加 BIN 和 生成卡片 +// ========================================== + +addBinBtn.addEventListener("click", () => { + const bin = binInput.value.trim(); + if (!bin) return; + + chrome.storage.local.get(["binHistory"], (data) => { + let history = data.binHistory || []; + // 去重并添加到头部 + history = history.filter(b => b !== bin); + history.unshift(bin); + // 限制历史记录数量 + if (history.length > 20) history = history.slice(0, 20); + + chrome.storage.local.set({ + binHistory: history, + currentBin: bin + }, () => { + loadBinHistory(); + showToast("BIN added to history"); + }); + }); +}); + +generateCardsBtn.addEventListener("click", async () => { + const bin = binInput.value.trim(); + if (!bin) { + showStatus("Please enter a BIN number", "error"); + return; + } + if (bin.length < 6) { + showStatus("BIN must be at least 6 digits", "error"); + return; + } + + const useLuhn = useLuhnValidation.checked; + + // UI 状态更新:处理中 + generateCardsBtn.disabled = true; + generateCardsBtn.innerHTML = "Processing..."; + + if (useLuhn) { + showStatus("🔐 Generating cards with Luhn validation...", "loading"); + } else { + showStatus("⚡ Generating cards...", "loading"); + } + + // 保存 BIN 历史 + chrome.storage.local.get(["binHistory"], (data) => { + let history = data.binHistory || []; + history = history.filter(b => b !== bin); + history.unshift(bin); + if (history.length > 20) history = history.slice(0, 20); + chrome.storage.local.set({ + binHistory: history, + currentBin: bin + }, () => { + loadBinHistory(); + }); + }); + + // 发送消息给后台生成卡片 + chrome.runtime.sendMessage({ + action: "generateCards", + bin: bin, + useValidation: useLuhn, + stripeTabId: null + }, (response) => { + if (response && response.success) { + const luhnMsg = useLuhn ? " (Luhn validated)" : ""; + showStatus("✅ Generated " + response.cards.length + " cards" + luhnMsg + ". Filling form...", "loading"); + + // 查找 Stripe 标签页并注入填充指令 + chrome.tabs.query({ + url: ["https://checkout.stripe.com/*", "https://*.stripe.com/*"] + }, (tabs) => { + if (chrome.runtime.lastError) { + resetGenerateButton(); + showStatus("❌ Error: " + chrome.runtime.lastError.message, "error"); + return; + } + + if (tabs.length > 0) { + // 优先选择当前激活的标签页 + const targetTab = tabs.find(t => t.active) || tabs[0]; + chrome.tabs.sendMessage(targetTab.id, { + action: "fillForm" + }, (fillResponse) => { + resetGenerateButton(); + if (chrome.runtime.lastError) { + showStatus("❌ No Stripe checkout page found. Please open one first.", "error"); + } else { + showStatus("Form filled!", "success"); + showToast("✅ Form filled successfully!"); + } + }); + } else { + resetGenerateButton(); + showStatus("❌ No Stripe checkout page found. Please open one first.", "error"); + } + }); + } else { + resetGenerateButton(); + showStatus("Failed to generate cards", "error"); + showToast("❌ Failed to generate cards. Try again.", "error"); + } + }); +}); + +function resetGenerateButton() { + generateCardsBtn.disabled = false; + generateCardsBtn.innerHTML = "🚀Fill Everything"; +} + +// ========================================== +// 历史记录管理 +// ========================================== + +function loadBinHistory() { + chrome.storage.local.get(["binHistory", "currentBin"], (data) => { + const history = data.binHistory || []; + const current = data.currentBin || DEFAULT_BIN; + + binInput.value = current; + binHistoryList.innerHTML = ""; + + if (history.length === 0) { + binHistoryList.innerHTML = "
No BINs saved yet
"; + return; + } + + history.forEach(bin => { + const item = document.createElement("div"); + item.className = "history-item"; + + const binText = document.createElement("span"); + binText.textContent = bin; + binText.className = "history-bin"; + binText.addEventListener("click", () => { + binInput.value = bin; + chrome.storage.local.set({ currentBin: bin }); + showToast("BIN selected"); + }); + + const delBtn = document.createElement("button"); + delBtn.textContent = "×"; + delBtn.className = "delete-btn"; + delBtn.addEventListener("click", (e) => { + e.stopPropagation(); + deleteBin(bin); + }); + + item.appendChild(binText); + item.appendChild(delBtn); + binHistoryList.appendChild(item); + }); + }); +} + +function deleteBin(bin) { + chrome.storage.local.get(["binHistory"], (data) => { + let history = data.binHistory || []; + history = history.filter(b => b !== bin); + chrome.storage.local.set({ binHistory: history }, () => { + loadBinHistory(); + showToast("BIN deleted"); + }); + }); +} + +// ========================================== +// 地址管理 +// ========================================== + +addAddressBtn.addEventListener("click", () => { + const name = nameInput.value.trim(); + const addr1 = address1Input.value.trim(); + const addr2 = address2Input.value.trim(); + const city = cityInput.value.trim(); + const state = stateInput.value.trim(); + const zip = zipInput.value.trim(); + + if (!name || !addr1 || !city || !state || !zip) { + showToast("Please fill all required fields", "error"); + return; + } + + // 简单的姓名拆分逻辑 + const nameParts = name.split(" "); + const first = nameParts[0] || name; + const last = nameParts.slice(1).join(" ") || nameParts[0]; + + const newAddr = { + id: Date.now(), + name: name, + firstName: first, + lastName: last, + address1: addr1, + address2: addr2, + city: city, + state: state, + stateCode: getStateCode(state), // 转换州简写 (如 California -> CA) + postal: zip, + countryText: "United States", + countryValue: "US" + }; + + chrome.storage.local.get(["customAddresses"], (data) => { + const list = data.customAddresses || []; + list.push(newAddr); + chrome.storage.local.set({ customAddresses: list }, () => { + clearAddressInputs(); + loadAddresses(); + showToast("Address added"); + }); + }); +}); + +function clearAddressInputs() { + nameInput.value = ""; + address1Input.value = ""; + address2Input.value = ""; + cityInput.value = ""; + stateInput.value = ""; + zipInput.value = ""; +} + +function loadAddresses() { + chrome.storage.local.get(["customAddresses"], (data) => { + const list = data.customAddresses || []; + addressesList.innerHTML = ""; + + if (list.length === 0) { + addressesList.innerHTML = "
No addresses saved yet
"; + return; + } + + list.forEach(addr => { + const item = document.createElement("div"); + item.className = "list-item"; + + const info = document.createElement("div"); + info.className = "item-info"; + info.innerHTML = ` + ${addr.name}
+ ${addr.address1}${addr.address2 ? ", " + addr.address2 : ""}
+ ${addr.city}, ${addr.state} ${addr.postal}
+ `; + + const delBtn = document.createElement("button"); + delBtn.textContent = "×"; + delBtn.className = "delete-btn"; + delBtn.addEventListener("click", () => deleteAddress(addr.id)); + + item.appendChild(info); + item.appendChild(delBtn); + addressesList.appendChild(item); + }); + }); +} + +function deleteAddress(id) { + chrome.storage.local.get(["customAddresses"], (data) => { + let list = data.customAddresses || []; + list = list.filter(item => item.id !== id); + chrome.storage.local.set({ customAddresses: list }, () => { + loadAddresses(); + showToast("Address deleted"); + }); + }); +} + +// ========================================== +// 姓名管理 +// ========================================== + +addNameBtn.addEventListener("click", () => { + const first = firstNameInput.value.trim(); + const last = lastNameInput.value.trim(); + + if (!first || !last) { + showToast("Please enter both first and last name", "error"); + return; + } + + const newName = { + id: Date.now(), + firstName: first, + lastName: last, + fullName: first + " " + last + }; + + chrome.storage.local.get(["customNames"], (data) => { + const list = data.customNames || []; + list.push(newName); + chrome.storage.local.set({ customNames: list }, () => { + firstNameInput.value = ""; + lastNameInput.value = ""; + loadNames(); + showToast("Name added"); + }); + }); +}); + +function loadNames() { + chrome.storage.local.get(["customNames"], (data) => { + const list = data.customNames || []; + namesList.innerHTML = ""; + + if (list.length === 0) { + namesList.innerHTML = "
No names saved yet
"; + return; + } + + list.forEach(item => { + const div = document.createElement("div"); + div.className = "list-item"; + + const info = document.createElement("div"); + info.className = "item-info"; + info.innerHTML = `${item.fullName}`; + + const delBtn = document.createElement("button"); + delBtn.textContent = "×"; + delBtn.className = "delete-btn"; + delBtn.addEventListener("click", () => deleteName(item.id)); + + div.appendChild(info); + div.appendChild(delBtn); + namesList.appendChild(div); + }); + }); +} + +function deleteName(id) { + chrome.storage.local.get(["customNames"], (data) => { + let list = data.customNames || []; + list = list.filter(item => item.id !== id); + chrome.storage.local.set({ customNames: list }, () => { + loadNames(); + showToast("Name deleted"); + }); + }); +} + +// ========================================== +// 辅助工具函数 +// ========================================== + +/** + * 将州全称转换为两字母缩写 (用于表单匹配) + */ +function getStateCode(stateName) { + const map = { + Alabama: "AL", Alaska: "AK", Arizona: "AZ", Arkansas: "AR", California: "CA", + Colorado: "CO", Connecticut: "CT", Delaware: "DE", Florida: "FL", Georgia: "GA", + Hawaii: "HI", Idaho: "ID", Illinois: "IL", Indiana: "IN", Iowa: "IA", + Kansas: "KS", Kentucky: "KY", Louisiana: "LA", Maine: "ME", Maryland: "MD", + Massachusetts: "MA", Michigan: "MI", Minnesota: "MN", Mississippi: "MS", Missouri: "MO", + Montana: "MT", Nebraska: "NE", Nevada: "NV", "New Hampshire": "NH", "New Jersey": "NJ", + "New Mexico": "NM", "New York": "NY", "North Carolina": "NC", "North Dakota": "ND", Ohio: "OH", + Oklahoma: "OK", Oregon: "OR", Pennsylvania: "PA", "Rhode Island": "RI", "South Carolina": "SC", + "South Dakota": "SD", Tennessee: "TN", Texas: "TX", Utah: "UT", Vermont: "VT", + Virginia: "VA", Washington: "WA", "West Virginia": "WV", Wisconsin: "WI", Wyoming: "WY" + }; + return map[stateName] || stateName.substring(0, 2).toUpperCase(); +} + +function showStatus(msg, type = "") { + statusMessage.textContent = msg; + statusMessage.className = "status-message " + type; + statusMessage.style.display = "block"; + + if (type === "success" || type === "error") { + setTimeout(() => { + statusMessage.style.display = "none"; + }, 5000); + } +} + +function showToast(msg, type = "success") { + const toast = document.createElement("div"); + toast.className = "toast " + type; + toast.textContent = msg; + document.body.appendChild(toast); + + // 动画显示 + setTimeout(() => { + toast.classList.add("show"); + }, 10); + + // 自动消失 + setTimeout(() => { + toast.classList.remove("show"); + setTimeout(() => toast.remove(), 300); + }, 2000); +} + +function loadData() { + loadBinHistory(); + loadAddresses(); + loadNames(); +} + +// ========================================== +// Telegram 广告弹窗逻辑 +// ========================================== + +const telegramModal = document.getElementById("telegramModal"); +const modalCloseBtn = document.getElementById("modalCloseBtn"); +const modalTimer = document.getElementById("modalTimer"); +let countdownInterval = null; +let autoCloseTimeout = null; + +function showTelegramModal() { + telegramModal.classList.add("show"); + let seconds = 5; + modalTimer.textContent = seconds; + + // 倒计时 + countdownInterval = setInterval(() => { + seconds--; + if (seconds > 0) { + modalTimer.textContent = seconds; + } else { + modalTimer.textContent = "0"; + clearInterval(countdownInterval); + } + }, 1000); + + // 5秒后自动关闭 + autoCloseTimeout = setTimeout(() => { + closeTelegramModal(); + }, 5000); +} + +function closeTelegramModal() { + telegramModal.classList.remove("show"); + if (countdownInterval) { + clearInterval(countdownInterval); + countdownInterval = null; + } + if (autoCloseTimeout) { + clearTimeout(autoCloseTimeout); + autoCloseTimeout = null; + } +} + +if (modalCloseBtn) { + modalCloseBtn.addEventListener("click", (e) => { + e.preventDefault(); + closeTelegramModal(); + }); +} + +// 延迟显示广告,避免一打开就弹 +if (telegramModal && modalCloseBtn && modalTimer) { + setTimeout(() => { + showTelegramModal(); + }, 300); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..230e49c --- /dev/null +++ b/styles.css @@ -0,0 +1,1486 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + /* Smooth Black & White Color Scheme */ + --bg-primary: #0a0a0a; + --bg-secondary: #1a1a1a; + --bg-tertiary: #2a2a2a; + --bg-glass: rgba(42, 42, 42, 0.7); + --accent-primary: #ffffff; + --accent-secondary: #e0e0e0; + --accent-tertiary: #c0c0c0; + --accent-gradient: linear-gradient(135deg, #ffffff 0%, #e0e0e0 50%, #ffffff 100%); + --accent-gradient-hover: linear-gradient(135deg, #f5f5f5 0%, #ffffff 50%, #f5f5f5 100%); + --text-primary: #ffffff; + --text-secondary: #d0d0d0; + --text-muted: #888888; + --border-color: #333333; + --border-light: #444444; + --border-glow: rgba(255, 255, 255, 0.1); + --success: #00ff88; + --error: #ff4444; + --warning: #ffaa00; + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.6); + --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.7); + --glow-white: 0 0 20px rgba(255, 255, 255, 0.1); +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; + background: var(--bg-primary); + background-image: + radial-gradient(at 0% 0%, rgba(255, 255, 255, 0.03) 0px, transparent 50%), + radial-gradient(at 100% 100%, rgba(255, 255, 255, 0.03) 0px, transparent 50%); + color: var(--text-primary); + min-width: 420px; + max-width: 600px; + min-height: 500px; + max-height: 700px; + margin: 0; + padding: 0; + overflow: hidden; +} + +.container { + background: var(--bg-primary); + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + position: relative; +} + +/* Header */ +.header { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--border-color); + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + position: relative; + box-shadow: var(--shadow-md); + min-height: 70px; +} + +.header-left { + display: flex; + align-items: center; + gap: 12px; + flex: 1; + min-width: 0; +} + +.logo { + font-size: 32px; + filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.3)); + animation: float 3s ease-in-out infinite; + flex-shrink: 0; +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px) scale(1); + filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.3)); + } + 50% { + transform: translateY(-4px) scale(1.05); + filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.5)); + } +} + +.header h2 { + font-size: 16px; + font-weight: 800; + color: var(--text-primary); + margin: 0; + letter-spacing: 1.5px; + text-transform: uppercase; + text-shadow: 0 0 10px rgba(255, 255, 255, 0.3); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; +} + +.branding { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 6px; + flex-shrink: 0; + margin-left: auto; + padding-left: 12px; +} + +.brand-name { + font-size: 10px; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 1px; + text-transform: uppercase; + text-shadow: 0 0 8px rgba(255, 255, 255, 0.4); + white-space: nowrap; +} + +.brand-links { + display: flex; + gap: 6px; + font-size: 9px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.brand-link { + color: var(--text-secondary); + text-decoration: none; + transition: all 0.3s ease; + padding: 3px 8px; + border-radius: 4px; + white-space: nowrap; + border: 1px solid transparent; +} + +.brand-link:hover { + color: var(--text-primary); + background: rgba(255, 255, 255, 0.1); + border-color: var(--border-light); + text-shadow: 0 0 6px rgba(255, 255, 255, 0.5); +} + +.version { + display: none; +} + +/* Tabs */ +.tabs { + display: flex; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + padding: 0 12px; + gap: 4px; + box-shadow: inset 0 -1px 4px rgba(0, 0, 0, 0.3); +} + +.tab-btn { + flex: 1; + padding: 14px 16px; + background: transparent; + border: none; + cursor: pointer; + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + border-radius: 12px 12px 0 0; +} + +.tab-btn::before { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%) scaleX(0); + width: 80%; + height: 2px; + background: var(--accent-primary); + border-radius: 2px 2px 0 0; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: var(--glow-white); +} + +.tab-btn:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); + transform: translateY(-2px); +} + +.tab-btn.active { + color: var(--accent-primary); + background: var(--bg-primary); +} + +.tab-btn.active::before { + transform: translateX(-50%) scaleX(1); +} + +.tab-icon { + font-size: 18px; + filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.3)); +} + +/* Content */ +.content { + flex: 1; + padding: 20px; + overflow-y: auto; + overflow-x: hidden; + background: var(--bg-primary); +} + +.tab-content { + display: none; + animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.tab-content.active { + display: block; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Sections */ +.section { + background: var(--bg-glass); + backdrop-filter: blur(20px); + border: 1px solid var(--border-color); + border-radius: 16px; + padding: 20px; + margin-bottom: 16px; + transition: all 0.3s ease; + box-shadow: var(--shadow-md); + position: relative; + overflow: hidden; +} + +.section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, var(--accent-primary), transparent); + opacity: 0; + transition: opacity 0.3s ease; +} + +.section:hover { + border-color: var(--border-light); + box-shadow: var(--shadow-lg); + transform: translateY(-2px); +} + +.section:hover::before { + opacity: 1; +} + +.section-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color); +} + +.section-icon { + font-size: 20px; + filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.3)); +} + +.section-header h3 { + font-size: 16px; + font-weight: 700; + color: var(--text-primary); + margin: 0; + letter-spacing: 0.5px; +} + +/* Sub Tabs (for Settings) */ +.sub-tabs { + display: flex; + gap: 10px; + margin-bottom: 16px; +} + +.sub-tab-btn { + flex: 1; + padding: 12px 16px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 12px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + position: relative; + overflow: hidden; +} + +.sub-tab-btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.1); + transition: left 0.3s ease; + z-index: 0; +} + +.sub-tab-btn span { + position: relative; + z-index: 1; +} + +.sub-tab-btn:hover { + border-color: var(--border-light); + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.sub-tab-btn.active { + background: rgba(255, 255, 255, 0.1); + color: var(--text-primary); + border-color: var(--accent-primary); + box-shadow: var(--shadow-lg), var(--glow-white); +} + +.sub-tab-btn.active::before { + left: 0; +} + +.sub-tab-content { + display: none; + animation: fadeInUp 0.3s ease; +} + +.sub-tab-content.active { + display: block; +} + +/* Input Groups */ +.input-group { + margin-bottom: 16px; + position: relative; +} + +.input-group label { + display: block; + margin-bottom: 10px; + font-weight: 700; + color: var(--text-secondary); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 1px; +} + +.input-with-btn { + display: flex; + gap: 10px; +} + +.input-with-btn input { + flex: 1; +} + +input.form-input, +.input-group input { + width: 100%; + padding: 14px 16px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 12px; + font-size: 14px; + color: var(--text-primary); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + font-family: 'Courier New', monospace; + box-shadow: var(--shadow-sm); +} + +input.form-input:focus, +.input-group input:focus { + outline: none; + border-color: var(--accent-primary); + background: var(--bg-secondary); + box-shadow: 0 0 0 4px var(--border-glow), var(--shadow-md); + transform: translateY(-1px); +} + +input::placeholder { + color: var(--text-muted); +} + +.input-row { + display: flex; + gap: 12px; +} + +.input-row input { + flex: 1; +} + +/* Checkbox */ +.checkbox-group { + margin: 16px 0; +} + +.checkbox-label { + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + padding: 14px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 12px; + transition: all 0.3s ease; +} + +.checkbox-label:hover { + border-color: var(--border-light); + background: var(--bg-secondary); + box-shadow: var(--shadow-md); +} + +.checkbox-label input[type="checkbox"] { + display: none; +} + +.checkbox-custom { + width: 22px; + height: 22px; + border: 2px solid var(--border-color); + border-radius: 6px; + position: relative; + transition: all 0.3s ease; + flex-shrink: 0; + background: var(--bg-secondary); +} + +.checkbox-label input[type="checkbox"]:checked + .checkbox-custom { + background: var(--accent-primary); + border-color: var(--accent-primary); + box-shadow: 0 0 12px rgba(255, 255, 255, 0.3); +} + +.checkbox-label input[type="checkbox"]:checked + .checkbox-custom::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--bg-primary); + font-weight: bold; + font-size: 14px; +} + +.checkbox-text { + display: flex; + flex-direction: column; + gap: 4px; + flex: 1; +} + +.checkbox-text small { + color: var(--text-muted); + font-size: 12px; +} + +/* Mini Settings Dropdown */ +.mini-settings { + display: none; + margin-top: 12px; + background: var(--bg-glass); + backdrop-filter: blur(20px); + border: 2px solid var(--border-color); + border-radius: 16px; + padding: 16px; + box-shadow: var(--shadow-xl); + animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1); + max-height: 400px; + overflow-y: auto; +} + +.mini-settings.show { + display: block; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.mini-settings-item { + padding: 0; + margin-bottom: 12px; +} + +.mini-settings-item:last-child { + margin-bottom: 0; +} + +.mini-settings-divider { + height: 1px; + background: linear-gradient(90deg, transparent, var(--border-color), transparent); + margin: 12px 0; +} + +.mini-settings-label { + font-size: 11px; + color: var(--text-secondary); + margin-bottom: 8px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; +} + +.mini-settings-hint { + font-size: 11px; + color: var(--text-muted); + margin-top: 6px; + font-style: italic; +} + +.mini-select { + width: 100%; + padding: 10px 12px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 10px; + color: var(--text-primary); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + outline: none; + font-family: inherit; +} + +.mini-select:hover { + border-color: var(--border-light); + background: var(--bg-secondary); + box-shadow: var(--shadow-sm); +} + +.mini-select:focus { + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px var(--border-glow); +} + +.mini-select option { + background: var(--bg-secondary); + color: var(--text-primary); + padding: 10px; +} + +.mini-select option:disabled { + color: var(--text-muted); +} + +.mini-checkbox-label { + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + padding: 12px 14px; + border-radius: 10px; + transition: all 0.3s ease; + user-select: none; +} + +.mini-checkbox-label:hover { + background: var(--bg-tertiary); +} + +.mini-checkbox-label:hover .mini-checkbox-custom { + border-color: var(--border-light); + box-shadow: 0 0 8px rgba(255, 255, 255, 0.2); +} + +.mini-checkbox-label input[type="checkbox"] { + display: none; +} + +.mini-checkbox-custom { + width: 20px; + height: 20px; + min-width: 20px; + min-height: 20px; + border: 2px solid var(--border-color); + border-radius: 5px; + position: relative; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-tertiary); +} + +.mini-checkbox-label input[type="checkbox"]:checked + .mini-checkbox-custom { + background: var(--accent-primary); + border-color: var(--accent-primary); + box-shadow: 0 0 12px rgba(255, 255, 255, 0.4); +} + +.mini-checkbox-label input[type="checkbox"]:checked + .mini-checkbox-custom::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(1); + color: var(--bg-primary); + font-weight: bold; + font-size: 14px; + line-height: 1; + animation: checkmarkPop 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +@keyframes checkmarkPop { + 0% { + transform: translate(-50%, -50%) scale(0); + opacity: 0; + } + 50% { + transform: translate(-50%, -50%) scale(1.3); + } + 100% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } +} + +.mini-checkbox-text { + color: var(--text-primary); + font-size: 14px; + font-weight: 500; + flex: 1; +} + +.mini-checkbox-label input[type="checkbox"]:checked ~ .mini-checkbox-text { + color: var(--accent-primary); +} + +/* Buttons */ +.icon-btn { + width: 48px; + height: 48px; + background: rgba(255, 255, 255, 0.1); + color: white; + border: 2px solid var(--border-color); + border-radius: 12px; + font-size: 22px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: var(--shadow-md); + position: relative; + overflow: hidden; +} + +.icon-btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.icon-btn:hover::before { + width: 300px; + height: 300px; +} + +.icon-btn:hover { + transform: translateY(-3px) scale(1.05); + box-shadow: var(--shadow-xl); + border-color: var(--accent-primary); + background: rgba(255, 255, 255, 0.15); +} + +.icon-btn:active { + transform: translateY(-1px) scale(1); +} + +.icon-btn.settings-btn { + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + font-size: 20px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.icon-btn.settings-btn:hover { + background: rgba(255, 255, 255, 0.15); + border-color: var(--accent-primary); + transform: translateY(-3px) rotate(90deg) scale(1.05); + box-shadow: var(--shadow-xl); +} + +.icon-btn.settings-btn.active { + background: rgba(255, 255, 255, 0.2); + border-color: var(--accent-primary); + box-shadow: var(--shadow-xl), var(--glow-white); + transform: rotate(180deg); +} + +.btn { + width: 100%; + padding: 16px 24px; + border: none; + border-radius: 12px; + font-size: 15px; + font-weight: 700; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + position: relative; + overflow: hidden; + letter-spacing: 0.5px; +} + +.btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.btn:hover::before { + width: 400px; + height: 400px; +} + +.btn-primary { + background: rgba(255, 255, 255, 0.1); + color: white; + border: 2px solid var(--accent-primary); + box-shadow: var(--shadow-lg), var(--glow-white); +} + +.btn-primary:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-xl), 0 0 30px rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.15); + border-color: var(--accent-primary); +} + +.btn-primary:active { + transform: translateY(-1px); +} + +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.btn-secondary { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 2px solid var(--border-color); + box-shadow: var(--shadow-sm); +} + +.btn-secondary:hover { + border-color: var(--border-light); + background: var(--bg-secondary); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.btn-icon { + font-size: 20px; + filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.3)); +} + +/* Form Groups */ +.form-group { + display: flex; + flex-direction: column; + gap: 14px; +} + +.form-group-compact { + display: flex; + flex-direction: column; + gap: 10px; +} + +.form-group-compact input { + padding: 11px 14px; + font-size: 13px; +} + +.btn-sm { + padding: 12px 20px; + font-size: 14px; +} + +/* History and Lists */ +.history-list { + max-height: 220px; + overflow-y: auto; +} + +.list-container { + max-height: 320px; + overflow-y: auto; +} + +.list-container-compact { + max-height: 280px; + overflow-y: auto; +} + +.history-item, +.list-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 12px; + margin-bottom: 8px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.history-item::before, +.list-item::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background: var(--accent-primary); + transform: scaleY(0); + transition: transform 0.3s ease; + box-shadow: var(--glow-white); +} + +.history-item:hover::before, +.list-item:hover::before { + transform: scaleY(1); +} + +.history-item:hover, +.list-item:hover { + background: var(--bg-secondary); + border-color: var(--border-light); + transform: translateX(6px); + box-shadow: var(--shadow-md); +} + +.history-bin { + font-family: 'Courier New', monospace; + font-size: 14px; + color: var(--text-primary); + cursor: pointer; + flex: 1; + font-weight: 600; +} + +.history-bin:hover { + color: var(--accent-primary); +} + +.item-info { + flex: 1; + font-size: 13px; + line-height: 1.7; +} + +.item-info strong { + color: var(--text-primary); + font-size: 15px; + font-weight: 700; +} + +.item-info small { + color: var(--text-secondary); +} + +.delete-btn { + width: 32px; + height: 32px; + background: rgba(255, 68, 68, 0.2); + color: var(--error); + border: 2px solid var(--error); + border-radius: 8px; + cursor: pointer; + font-size: 18px; + line-height: 1; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: var(--shadow-sm); +} + +.delete-btn:hover { + background: var(--error); + color: white; + transform: scale(1.15) rotate(90deg); + box-shadow: var(--shadow-md), 0 0 15px rgba(255, 68, 68, 0.5); +} + +.empty { + text-align: center; + padding: 50px 20px; + color: var(--text-muted); + font-size: 14px; + font-style: italic; +} + +/* Status Message */ +.status-message { + display: none; + padding: 14px 18px; + border-radius: 12px; + margin-top: 18px; + font-size: 13px; + text-align: center; + font-weight: 600; + border: 2px solid; + backdrop-filter: blur(10px); +} + +.status-message.loading { + background: rgba(255, 170, 0, 0.1); + color: var(--warning); + border-color: var(--warning); + box-shadow: 0 0 16px rgba(255, 170, 0, 0.2); +} + +.status-message.success { + background: rgba(0, 255, 136, 0.1); + color: var(--success); + border-color: var(--success); + box-shadow: 0 0 16px rgba(0, 255, 136, 0.2); +} + +.status-message.error { + background: rgba(255, 68, 68, 0.1); + color: var(--error); + border-color: var(--error); + box-shadow: 0 0 16px rgba(255, 68, 68, 0.2); +} + +/* Toast Notifications */ +.toast { + position: fixed; + bottom: 24px; + right: 24px; + background: var(--bg-glass); + backdrop-filter: blur(20px); + border: 2px solid var(--border-color); + color: var(--text-primary); + padding: 14px 24px; + border-radius: 12px; + font-size: 13px; + font-weight: 700; + box-shadow: var(--shadow-xl); + opacity: 0; + transform: translateY(30px) scale(0.9); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 10000; +} + +.toast.show { + opacity: 1; + transform: translateY(0) scale(1); +} + +.toast.success { + border-color: var(--success); + box-shadow: 0 8px 32px rgba(0, 255, 136, 0.3); +} + +.toast.error { + border-color: var(--error); + box-shadow: 0 8px 32px rgba(255, 68, 68, 0.3); +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 10px; + transition: background 0.3s ease; + border: 2px solid var(--bg-tertiary); +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* Animations */ +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +/* BIN List Styles */ +.bin-search { + margin-bottom: 16px; +} + +.bin-categories { + display: flex; + gap: 10px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.bin-cat-btn { + padding: 10px 16px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 10px; + color: var(--text-secondary); + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.bin-cat-btn:hover { + background: var(--bg-secondary); + color: var(--text-primary); + border-color: var(--border-light); +} + +.bin-cat-btn.active { + background: rgba(255, 255, 255, 0.1); + color: white; + border-color: var(--accent-primary); + box-shadow: var(--shadow-md); +} + +.bin-list-grid { + display: grid; + grid-template-columns: 1fr; + gap: 10px; + max-height: 420px; + overflow-y: auto; +} + +.bin-card { + background: var(--bg-glass); + backdrop-filter: blur(20px); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 14px; + cursor: pointer; + transition: all 0.3s ease; +} + +.bin-card:hover { + background: var(--bg-secondary); + border-color: var(--border-light); + transform: translateX(4px); + box-shadow: var(--shadow-md); +} + +.bin-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.bin-number { + font-family: 'Courier New', monospace; + font-size: 15px; + font-weight: 700; + color: var(--accent-primary); + text-shadow: 0 0 8px rgba(255, 255, 255, 0.3); +} + +.bin-type { + font-size: 11px; + padding: 4px 10px; + background: var(--bg-tertiary); + border-radius: 6px; + color: var(--text-secondary); + font-weight: 600; +} + +.bin-card-body { + display: flex; + flex-direction: column; + gap: 6px; +} + +.bin-info { + font-size: 12px; + color: var(--text-secondary); +} + +.bin-bank { + font-weight: 600; + color: var(--text-primary); +} + +.bin-country { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + color: var(--text-muted); +} + +/* Footer/Branding Section */ +.footer-branding { + margin-top: 20px; + padding: 16px; + background: var(--bg-glass); + backdrop-filter: blur(20px); + border: 1px solid var(--border-color); + border-radius: 12px; + text-align: center; +} + +.footer-branding-title { + font-size: 12px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8px; + letter-spacing: 1px; + text-transform: uppercase; +} + +.footer-branding-links { + display: flex; + justify-content: center; + gap: 12px; + flex-wrap: wrap; + margin-top: 8px; +} + +.footer-branding-link { + color: var(--text-secondary); + text-decoration: none; + font-size: 11px; + padding: 6px 12px; + border: 1px solid var(--border-color); + border-radius: 6px; + transition: all 0.3s ease; +} + +.footer-branding-link:hover { + color: var(--text-primary); + border-color: var(--accent-primary); + background: rgba(255, 255, 255, 0.05); + transform: translateY(-2px); +} + +/* Responsive adjustments */ +@media (min-width: 500px) { + body { + min-width: 480px; + } + + .content { + padding: 24px; + } + + .section { + padding: 24px; + } +} + +@media (max-height: 600px) { + .header { + padding: 14px 18px; + } + + .tab-btn { + padding: 12px 14px; + } + + .content { + padding: 16px; + } + + .section { + padding: 16px; + margin-bottom: 12px; + } + + .history-list { + max-height: 140px; + } + + .list-container-compact { + max-height: 180px; + } +} + +/* Telegram Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + opacity: 0; + visibility: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.modal-overlay.show { + opacity: 1; + visibility: visible; +} + +.modal-content { + background: var(--bg-glass); + backdrop-filter: blur(20px); + border: 2px solid var(--border-color); + border-radius: 20px; + padding: 24px; + max-width: 400px; + width: 90%; + position: relative; + box-shadow: var(--shadow-xl), 0 0 40px rgba(255, 255, 255, 0.1); + transform: scale(0.9) translateY(20px); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.modal-overlay.show .modal-content { + transform: scale(1) translateY(0); +} + +.modal-close { + position: absolute; + top: 12px; + right: 12px; + width: 32px; + height: 32px; + background: rgba(255, 255, 255, 0.1); + border: 2px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 20px; + font-weight: 700; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + line-height: 1; + padding: 0; +} + +.modal-close:hover { + background: rgba(255, 255, 255, 0.2); + border-color: var(--accent-primary); + transform: rotate(90deg) scale(1.1); + box-shadow: var(--shadow-md); +} + +.modal-header { + text-align: center; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--border-color); +} + +.modal-icon { + font-size: 48px; + margin-bottom: 12px; + filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.3)); + animation: float 3s ease-in-out infinite; +} + +.modal-title { + font-size: 20px; + font-weight: 700; + color: var(--text-primary); + margin: 0; + letter-spacing: 1px; + text-transform: uppercase; + text-shadow: 0 0 10px rgba(255, 255, 255, 0.3); +} + +.modal-body { + display: flex; + flex-direction: column; + gap: 16px; +} + +.modal-description { + font-size: 13px; + color: var(--text-secondary); + text-align: center; + margin: 0; + line-height: 1.6; +} + +.modal-links { + display: flex; + flex-direction: column; + gap: 12px; +} + +.modal-link { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 18px; + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 12px; + text-decoration: none; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.modal-link::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.05); + transition: left 0.3s ease; +} + +.modal-link:hover::before { + left: 0; +} + +.modal-link:hover { + border-color: var(--accent-primary); + background: var(--bg-secondary); + transform: translateX(4px); + box-shadow: var(--shadow-md), 0 0 20px rgba(255, 255, 255, 0.1); +} + +.modal-link-icon { + font-size: 28px; + flex-shrink: 0; + filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.3)); +} + +.modal-link-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +} + +.modal-link-title { + font-size: 14px; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 0.5px; +} + +.modal-link-subtitle { + font-size: 11px; + color: var(--text-secondary); + font-weight: 500; +} + +.telegram-link:hover .modal-link-icon { + animation: pulse 1s ease-in-out infinite; +} + +.temp-link:hover .modal-link-icon { + animation: pulse 1s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } +} + +.modal-timer { + text-align: center; + padding-top: 12px; + border-top: 1px solid var(--border-color); + font-size: 12px; + color: var(--text-muted); + font-weight: 600; +} + +.modal-timer span { + color: var(--accent-primary); + font-size: 14px; + font-weight: 700; + text-shadow: 0 0 8px rgba(255, 255, 255, 0.3); +}