解混淆
This commit is contained in:
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@@ -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
|
||||
|
||||
145
CONTRIBUTING.md
Normal file
145
CONTRIBUTING.md
Normal file
@@ -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! 🎉
|
||||
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -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.
|
||||
|
||||
546
README.md
Normal file
546
README.md
Normal file
@@ -0,0 +1,546 @@
|
||||
# 💳 Cardbingenerator - Stripe Auto Fill
|
||||
|
||||
[](https://github.com/yourusername/cardbingenerator-extension)
|
||||
[](LICENSE)
|
||||
[](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 扩展社区
|
||||
- 所有贡献者和测试人员
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<strong>记住:本工具仅用于测试目的。请始终尊重法律和你正在测试的平台的服务条款。</strong>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 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.**
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<strong>Remember: This tool is for testing purposes only. Always respect the law and terms of service of the platforms you're testing on.</strong>
|
||||
</div>
|
||||
550
background.js
Normal file
550
background.js
Normal file
@@ -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 });
|
||||
}
|
||||
}
|
||||
880
content.js
Normal file
880
content.js
Normal file
@@ -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");
|
||||
});
|
||||
BIN
icon128.png
Normal file
BIN
icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
icon16.png
Normal file
BIN
icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 703 B |
BIN
icon48.png
Normal file
BIN
icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
43
manifest.json
Normal file
43
manifest.json
Normal file
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
249
popup.html
Normal file
249
popup.html
Normal file
@@ -0,0 +1,249 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>cardbingenerator</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Telegram Promotion Modal -->
|
||||
<div id="telegramModal" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<button class="modal-close" id="modalCloseBtn">×</button>
|
||||
<div class="modal-header">
|
||||
<div class="modal-icon">📱</div>
|
||||
<h3 class="modal-title">Join Our Community</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="modal-description">Get access to more hacks, tools, and exclusive content!</p>
|
||||
<div class="modal-links">
|
||||
<a href="https://t.me/+VJJt9csJoEUxZTA1" target="_blank" class="modal-link telegram-link">
|
||||
<span class="modal-link-icon">💬</span>
|
||||
<div class="modal-link-content">
|
||||
<div class="modal-link-title">Join Telegram Group</div>
|
||||
<div class="modal-link-subtitle">More Hacks & Tools</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://t.me/Tempotpsms_bot" target="_blank" class="modal-link temp-link">
|
||||
<span class="modal-link-icon">📞</span>
|
||||
<div class="modal-link-content">
|
||||
<div class="modal-link-title">Temp Number Service</div>
|
||||
<div class="modal-link-subtitle">Virtual SMS Bot</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-timer">
|
||||
<span id="modalTimer">5</span>s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="logo">💳</div>
|
||||
<h2>cardbingenerator</h2>
|
||||
</div>
|
||||
<div class="branding">
|
||||
<div class="brand-name">Blackhat_bullet</div>
|
||||
<div class="brand-links">
|
||||
<a href="https://www.youtube.com/@Blackhat_bullet" target="_blank" class="brand-link">YouTube</a>
|
||||
<a href="https://cardbingenerator.com/" target="_blank" class="brand-link">BIN Site</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" data-tab="general">
|
||||
<span class="tab-icon">⚡</span>
|
||||
<span>General</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="binlist">
|
||||
<span class="tab-icon">💎</span>
|
||||
<span>BIN List</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="settings">
|
||||
<span class="tab-icon">⚙️</span>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- General Tab -->
|
||||
<div id="general-tab" class="tab-content active">
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<span class="section-icon">💳</span>
|
||||
<h3>Card Generator</h3>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="binInput">BIN Template:</label>
|
||||
<div class="input-with-btn">
|
||||
<input type="text" id="binInput" placeholder="Enter BIN (e.g. 552461)" maxlength="19">
|
||||
<button id="addBinBtn" class="icon-btn" title="Save to history">
|
||||
<span>+</span>
|
||||
</button>
|
||||
<button id="settingsToggleBtn" class="icon-btn settings-btn" title="Settings">
|
||||
<span>⚙️</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mini settings dropdown -->
|
||||
<div id="miniSettings" class="mini-settings">
|
||||
<div class="mini-settings-item">
|
||||
<label class="mini-checkbox-label">
|
||||
<input type="checkbox" id="useLuhnValidation" checked>
|
||||
<span class="mini-checkbox-custom"></span>
|
||||
<span class="mini-checkbox-text">Card Validation</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mini-settings-divider"></div>
|
||||
|
||||
<div class="mini-settings-item">
|
||||
<div class="mini-settings-label">🏠 Address Source:</div>
|
||||
<select id="addressSourceSelect" class="mini-select">
|
||||
<option value="static">Static (Built-in)</option>
|
||||
<option value="manual">Manual (Custom)</option>
|
||||
<option value="auto" disabled>Auto (Coming Soon)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mini-settings-item">
|
||||
<div class="mini-settings-label">👤 Name Source:</div>
|
||||
<select id="nameSourceSelect" class="mini-select">
|
||||
<option value="static">Static (Built-in)</option>
|
||||
<option value="manual">Manual (Custom)</option>
|
||||
<option value="auto" disabled>Auto (Coming Soon)</option>
|
||||
</select>
|
||||
<div class="mini-settings-hint">Names are taken from addresses</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small style="color: var(--text-muted); font-size: 12px; margin-top: 4px; display: block;">
|
||||
Auto-fills with 'x' to 16 digits • Max 19 digits for long cards
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<button id="generateCardsBtn" class="btn btn-primary">
|
||||
<span class="btn-icon">🚀</span>
|
||||
<span>Fill Everything</span>
|
||||
</button>
|
||||
|
||||
<div class="status-message" id="statusMessage"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<span class="section-icon">📜</span>
|
||||
<h3>History</h3>
|
||||
</div>
|
||||
<div id="binHistoryList" class="history-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BIN List Tab -->
|
||||
<div id="binlist-tab" class="tab-content">
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<span class="section-icon">💎</span>
|
||||
<h3>BIN Generator</h3>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; gap: 20px;">
|
||||
<div style="font-size: 64px; opacity: 0.4;">💎</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: var(--text-primary); text-align: center;">
|
||||
Powered by cardbingenerator
|
||||
</div>
|
||||
<div style="font-size: 13px; color: var(--text-secondary); text-align: center; max-width: 320px; line-height: 1.6;">
|
||||
Visit <a href="https://cardbingenerator.com/" target="_blank" style="color: var(--accent-primary); text-decoration: none; font-weight: 600;">cardbingenerator.com</a> for advanced BIN generation with multiple card networks, formats, and validation options.
|
||||
</div>
|
||||
<a href="https://cardbingenerator.com/" target="_blank" class="btn btn-primary" style="margin-top: 10px; text-decoration: none; display: inline-flex; width: auto; padding: 12px 24px;">
|
||||
<span>Visit BIN Generator</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-branding">
|
||||
<div class="footer-branding-title">Powered by Blackhat_bullet</div>
|
||||
<div class="footer-branding-links">
|
||||
<a href="https://www.youtube.com/@Blackhat_bullet" target="_blank" class="footer-branding-link">📺 YouTube Channel</a>
|
||||
<a href="https://t.me/+VJJt9csJoEUxZTA1" target="_blank" class="footer-branding-link">💬 Telegram Group</a>
|
||||
<a href="https://t.me/Tempotpsms_bot" target="_blank" class="footer-branding-link">📞 Temp SMS</a>
|
||||
<a href="https://cardbingenerator.com/" target="_blank" class="footer-branding-link">💳 BIN Generator</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Tab -->
|
||||
<div id="settings-tab" class="tab-content">
|
||||
<div class="sub-tabs">
|
||||
<button class="sub-tab-btn active" data-subtab="addresses">
|
||||
<span>🏠 Addresses</span>
|
||||
</button>
|
||||
<button class="sub-tab-btn" data-subtab="names">
|
||||
<span>👤 Names</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Addresses SubTab -->
|
||||
<div id="addresses-subtab" class="sub-tab-content active">
|
||||
<div class="form-group-compact">
|
||||
<input type="text" id="nameInput" placeholder="Full Name" class="form-input">
|
||||
<input type="text" id="address1Input" placeholder="Address Line 1" class="form-input">
|
||||
<input type="text" id="address2Input" placeholder="Address Line 2 (optional)" class="form-input">
|
||||
<div class="input-row">
|
||||
<input type="text" id="cityInput" placeholder="City" class="form-input">
|
||||
<input type="text" id="stateInput" placeholder="State" class="form-input">
|
||||
</div>
|
||||
<input type="text" id="zipInput" placeholder="ZIP Code" class="form-input">
|
||||
<button id="addAddressBtn" class="btn btn-secondary btn-sm">
|
||||
<span class="btn-icon">+</span>
|
||||
<span>Add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="list-container-compact">
|
||||
<div id="addressesList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Names SubTab -->
|
||||
<div id="names-subtab" class="sub-tab-content">
|
||||
<div class="form-group-compact">
|
||||
<div class="input-row">
|
||||
<input type="text" id="firstNameInput" placeholder="First Name" class="form-input">
|
||||
<input type="text" id="lastNameInput" placeholder="Last Name" class="form-input">
|
||||
</div>
|
||||
<button id="addNameBtn" class="btn btn-secondary btn-sm">
|
||||
<span class="btn-icon">+</span>
|
||||
<span>Add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="list-container-compact">
|
||||
<div id="namesList"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Branding -->
|
||||
<div class="footer-branding">
|
||||
<div class="footer-branding-title">Blackhat_bullet</div>
|
||||
<div style="font-size: 11px; color: var(--text-muted); margin-bottom: 8px;">
|
||||
Follow us for more tools, hacks, and tutorials
|
||||
</div>
|
||||
<div class="footer-branding-links">
|
||||
<a href="https://www.youtube.com/@Blackhat_bullet" target="_blank" class="footer-branding-link">📺 YouTube: Stuffs</a>
|
||||
<a href="https://t.me/+VJJt9csJoEUxZTA1" target="_blank" class="footer-branding-link">💬 Telegram Group</a>
|
||||
<a href="https://t.me/Tempotpsms_bot" target="_blank" class="footer-branding-link">📞 Temp SMS Bot</a>
|
||||
<a href="https://cardbingenerator.com/" target="_blank" class="footer-branding-link">💳 BIN Generator</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
627
popup.js
Normal file
627
popup.js
Normal file
@@ -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 = "<span class=\"btn-icon\">⏳</span><span>Processing...</span>";
|
||||
|
||||
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 = "<span class=\"btn-icon\">🚀</span><span>Fill Everything</span>";
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 历史记录管理
|
||||
// ==========================================
|
||||
|
||||
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 = "<div class=\"empty\">No BINs saved yet</div>";
|
||||
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 = "<div class=\"empty\">No addresses saved yet</div>";
|
||||
return;
|
||||
}
|
||||
|
||||
list.forEach(addr => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "list-item";
|
||||
|
||||
const info = document.createElement("div");
|
||||
info.className = "item-info";
|
||||
info.innerHTML = `
|
||||
<strong>${addr.name}</strong><br>
|
||||
<small>${addr.address1}${addr.address2 ? ", " + addr.address2 : ""}<br>
|
||||
${addr.city}, ${addr.state} ${addr.postal}</small>
|
||||
`;
|
||||
|
||||
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 = "<div class=\"empty\">No names saved yet</div>";
|
||||
return;
|
||||
}
|
||||
|
||||
list.forEach(item => {
|
||||
const div = document.createElement("div");
|
||||
div.className = "list-item";
|
||||
|
||||
const info = document.createElement("div");
|
||||
info.className = "item-info";
|
||||
info.innerHTML = `<strong>${item.fullName}</strong>`;
|
||||
|
||||
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);
|
||||
}
|
||||
1486
styles.css
Normal file
1486
styles.css
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user