├── .env-example ├── .gitignore ├── LICENSE ├── README.md ├── data └── token-example.json ├── filesConfig-example.json ├── package.json ├── packages ├── chain-module │ ├── evm-script │ │ ├── README.md │ │ ├── index.js │ │ └── trade.js │ ├── sol-script │ │ ├── README.md │ │ ├── index.js │ │ └── umi.js │ ├── sui-script │ │ ├── README.md │ │ └── suiKit.js │ └── utxo-script │ │ ├── README.md │ │ ├── function.js │ │ └── index.js ├── crypt-module │ ├── README.md │ ├── crypt.js │ └── onepassword.js ├── exchange-module │ ├── README.md │ ├── binance.js │ ├── bybit.js │ └── okx.js ├── notification-module │ ├── README.md │ ├── notification.js │ └── notifier.js ├── rpa-module │ ├── README.md │ ├── bitbrowser │ │ ├── bitbrowser.js │ │ ├── bitbrowserSelenium.js │ │ ├── keplr.js │ │ ├── metamask.js │ │ ├── phantom.js │ │ └── unisat.js │ ├── browserConfig.js │ ├── chrome │ │ └── chromeBrowser │ │ │ ├── README.md │ │ │ ├── chromeBrowser.js │ │ │ ├── chromeProfile.js │ │ │ ├── config.js │ │ │ ├── image │ │ │ ├── avatar │ │ │ │ ├── Chrome001.png │ │ │ │ ├── Chrome002.png │ │ │ │ ├── Chrome003.png │ │ │ │ ├── Chrome004.png │ │ │ │ ├── Chrome005.png │ │ │ │ ├── Chrome006.png │ │ │ │ ├── Chrome007.png │ │ │ │ ├── Chrome008.png │ │ │ │ ├── Chrome009.png │ │ │ │ ├── Chrome010.png │ │ │ │ ├── Chrome011.png │ │ │ │ ├── Chrome012.png │ │ │ │ ├── Chrome013.png │ │ │ │ ├── Chrome014.png │ │ │ │ ├── Chrome015.png │ │ │ │ ├── Chrome016.png │ │ │ │ ├── Chrome017.png │ │ │ │ ├── Chrome018.png │ │ │ │ ├── Chrome019.png │ │ │ │ ├── Chrome020.png │ │ │ │ ├── Chrome021.png │ │ │ │ ├── Chrome022.png │ │ │ │ ├── Chrome023.png │ │ │ │ ├── Chrome024.png │ │ │ │ ├── Chrome025.png │ │ │ │ ├── Chrome026.png │ │ │ │ ├── Chrome027.png │ │ │ │ ├── Chrome028.png │ │ │ │ ├── Chrome029.png │ │ │ │ ├── Chrome030.png │ │ │ │ ├── Chrome031.png │ │ │ │ ├── Chrome032.png │ │ │ │ ├── Chrome033.png │ │ │ │ ├── Chrome034.png │ │ │ │ ├── Chrome035.png │ │ │ │ ├── Chrome036.png │ │ │ │ ├── Chrome037.png │ │ │ │ ├── Chrome038.png │ │ │ │ ├── Chrome039.png │ │ │ │ ├── Chrome040.png │ │ │ │ ├── Chrome041.png │ │ │ │ ├── Chrome042.png │ │ │ │ ├── Chrome043.png │ │ │ │ ├── Chrome044.png │ │ │ │ ├── Chrome045.png │ │ │ │ ├── Chrome046.png │ │ │ │ ├── Chrome047.png │ │ │ │ ├── Chrome048.png │ │ │ │ ├── Chrome049.png │ │ │ │ └── Chrome050.png │ │ │ └── icons │ │ │ │ ├── icons │ │ │ │ ├── chrome.icns │ │ │ │ ├── chrome001.icns │ │ │ │ ├── chrome002.icns │ │ │ │ ├── chrome003.icns │ │ │ │ ├── chrome004.icns │ │ │ │ ├── chrome005.icns │ │ │ │ ├── chrome006.icns │ │ │ │ ├── chrome007.icns │ │ │ │ ├── chrome008.icns │ │ │ │ ├── chrome009.icns │ │ │ │ ├── chrome010.icns │ │ │ │ ├── chrome011.icns │ │ │ │ ├── chrome012.icns │ │ │ │ ├── chrome013.icns │ │ │ │ ├── chrome014.icns │ │ │ │ ├── chrome015.icns │ │ │ │ ├── chrome016.icns │ │ │ │ ├── chrome017.icns │ │ │ │ ├── chrome018.icns │ │ │ │ ├── chrome019.icns │ │ │ │ ├── chrome020.icns │ │ │ │ ├── chrome021.icns │ │ │ │ ├── chrome022.icns │ │ │ │ ├── chrome023.icns │ │ │ │ ├── chrome024.icns │ │ │ │ ├── chrome025.icns │ │ │ │ ├── chrome026.icns │ │ │ │ ├── chrome027.icns │ │ │ │ ├── chrome028.icns │ │ │ │ ├── chrome029.icns │ │ │ │ ├── chrome030.icns │ │ │ │ ├── chrome031.icns │ │ │ │ ├── chrome032.icns │ │ │ │ ├── chrome033.icns │ │ │ │ ├── chrome034.icns │ │ │ │ ├── chrome035.icns │ │ │ │ ├── chrome036.icns │ │ │ │ ├── chrome037.icns │ │ │ │ ├── chrome038.icns │ │ │ │ ├── chrome039.icns │ │ │ │ ├── chrome040.icns │ │ │ │ ├── chrome041.icns │ │ │ │ ├── chrome042.icns │ │ │ │ ├── chrome043.icns │ │ │ │ ├── chrome044.icns │ │ │ │ ├── chrome045.icns │ │ │ │ ├── chrome046.icns │ │ │ │ ├── chrome047.icns │ │ │ │ ├── chrome048.icns │ │ │ │ ├── chrome049.icns │ │ │ │ └── chrome050.icns │ │ │ │ └── png │ │ │ │ ├── chrome.png │ │ │ │ ├── chrome001.png │ │ │ │ ├── chrome002.png │ │ │ │ ├── chrome003.png │ │ │ │ ├── chrome004.png │ │ │ │ ├── chrome005.png │ │ │ │ ├── chrome006.png │ │ │ │ ├── chrome007.png │ │ │ │ ├── chrome008.png │ │ │ │ ├── chrome009.png │ │ │ │ ├── chrome010.png │ │ │ │ ├── chrome011.png │ │ │ │ ├── chrome012.png │ │ │ │ ├── chrome013.png │ │ │ │ ├── chrome014.png │ │ │ │ ├── chrome015.png │ │ │ │ ├── chrome016.png │ │ │ │ ├── chrome017.png │ │ │ │ ├── chrome018.png │ │ │ │ ├── chrome019.png │ │ │ │ ├── chrome020.png │ │ │ │ ├── chrome021.png │ │ │ │ ├── chrome022.png │ │ │ │ ├── chrome023.png │ │ │ │ ├── chrome024.png │ │ │ │ ├── chrome025.png │ │ │ │ ├── chrome026.png │ │ │ │ ├── chrome027.png │ │ │ │ ├── chrome028.png │ │ │ │ ├── chrome029.png │ │ │ │ ├── chrome030.png │ │ │ │ ├── chrome031.png │ │ │ │ ├── chrome032.png │ │ │ │ ├── chrome033.png │ │ │ │ ├── chrome034.png │ │ │ │ ├── chrome035.png │ │ │ │ ├── chrome036.png │ │ │ │ ├── chrome037.png │ │ │ │ ├── chrome038.png │ │ │ │ ├── chrome039.png │ │ │ │ ├── chrome040.png │ │ │ │ ├── chrome041.png │ │ │ │ ├── chrome042.png │ │ │ │ ├── chrome043.png │ │ │ │ ├── chrome044.png │ │ │ │ ├── chrome045.png │ │ │ │ ├── chrome046.png │ │ │ │ ├── chrome047.png │ │ │ │ ├── chrome048.png │ │ │ │ ├── chrome049.png │ │ │ │ └── chrome050.png │ │ │ ├── proxyManger.js │ │ │ └── proxyServer.js │ └── okxWallet.js ├── social-module │ ├── email │ │ ├── README.md │ │ ├── gmail.js │ │ └── outlook.js │ ├── galxe │ │ └── galxe.js │ └── x │ │ ├── README.md │ │ └── x.js └── utils-module │ ├── README.md │ ├── captcha.js │ ├── check.js │ ├── formatdata.js │ ├── generateWallet.js │ ├── otp.js │ ├── path.js │ ├── retry.js │ └── utils.js ├── pnpm-lock.yaml └── projects ├── README.md ├── monad └── faucet.js └── saharaAi ├── faucet.js └── saharaAi.js /.env-example: -------------------------------------------------------------------------------- 1 | # personalToken是加解密密码,存储在1password中。使用的时候通过指纹来解锁1password获取personalToken 2 | # 示例:personalToken存储1password的blockchain保险库中。根据自己的实际情况修改路径 3 | personalToken = 'op://blockchain/personalToken' 4 | 5 | # 各钱包密码 6 | okxWalletPassword = 'op://blockchain/okxwallet/password' 7 | phantomPassword = 'op://blockchain/phantom/password' 8 | unsatPassword = 'op://blockchain/unisat/password' 9 | keplrPassword = 'op://blockchain/keplr/password' 10 | 11 | 12 | # infura的apiKey 13 | infuraKey = 'xxxxxxxxxx' 14 | # alchemy的apiKey 15 | alchemyKey = 'xxxxxxxxxx' 16 | 17 | # sol的rpc,轮换使用 18 | # helius的apiKey 19 | heliusKey = 'xxxxxxxxxx' 20 | # quickNode的apiKey 21 | quickNodeKey = 'xxxxxxxxxx' 22 | 23 | # 各人机验证服务商的clientKey 24 | yesCaptchaClientKey = 'xxxxxxxxxx' 25 | noCaptchaClientKey = 'xxxxxxxxxx' 26 | capSolverClientKey = 'xxxxxxxxxx' 27 | 28 | # gmail邮箱的clientId和clientSecret 29 | gmailClientId = 'xxxxxxxxxx' 30 | gmailClientSecret = 'xxxxxxxxxx' 31 | gmailRedirectUri = 'https://mail.google.com' 32 | 33 | # outlook邮箱的clientId和clientSecret 34 | outlookClientId = 'xxxxxxxxxx' 35 | outlookClientSecret = 'xxxxxxxxxx' 36 | outlookRedirectUri = 'http://localhost:3000/auth/redirect' 37 | 38 | # x的clientId和clientSecret 39 | xClientId = 'xxxxxxxxxx' 40 | xClientSecret = 'xxxxxxxxxx' 41 | xRedirectUri = 'https://x.com/home' 42 | 43 | # 钉钉机器人access_token。发送消息用于通知 44 | dingtalkAccessToken = 'xxxxxxxxxx' 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data/* 2 | !data/token-example.json 3 | filesConfig.json 4 | 5 | # Example JS files 6 | *[eE]xample*.js 7 | example*.js 8 | *[eE]xample.js 9 | 10 | .DS_Store 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | .pnpm-debug.log* 20 | 21 | # Diagnostic reports (https://nodejs.org/api/report.html) 22 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | *.pid.lock 29 | 30 | # Directory for instrumented libs generated by jscoverage/JSCover 31 | lib-cov 32 | 33 | # Coverage directory used by tools like istanbul 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | .nyc_output 39 | 40 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 41 | .grunt 42 | 43 | # Bower dependency directory (https://bower.io/) 44 | bower_components 45 | 46 | # node-waf configuration 47 | .lock-wscript 48 | 49 | # Compiled binary addons (https://nodejs.org/api/addons.html) 50 | build/Release 51 | 52 | # Dependency directories 53 | node_modules/ 54 | jspm_packages/ 55 | 56 | # Snowpack dependency directory (https://snowpack.dev/) 57 | web_modules/ 58 | 59 | # TypeScript cache 60 | *.tsbuildinfo 61 | 62 | # Optional npm cache directory 63 | .npm 64 | 65 | # Optional eslint cache 66 | .eslintcache 67 | 68 | # Optional stylelint cache 69 | .stylelintcache 70 | 71 | # Microbundle cache 72 | .rpt2_cache/ 73 | .rts2_cache_cjs/ 74 | .rts2_cache_es/ 75 | .rts2_cache_umd/ 76 | 77 | # Optional REPL history 78 | .node_repl_history 79 | 80 | # Output of 'npm pack' 81 | *.tgz 82 | 83 | # Yarn Integrity file 84 | .yarn-integrity 85 | 86 | # dotenv environment variable files 87 | .env 88 | .env.development.local 89 | .env.test.local 90 | .env.production.local 91 | .env.local 92 | 93 | # parcel-bundler cache (https://parceljs.org/) 94 | .cache 95 | .parcel-cache 96 | 97 | # Next.js build output 98 | .next 99 | out 100 | 101 | # Nuxt.js build / generate output 102 | .nuxt 103 | dist 104 | 105 | # Gatsby files 106 | .cache/ 107 | # Comment in the public line in if your project uses Gatsby and not Next.js 108 | # https://nextjs.org/blog/next-9-1#public-directory-support 109 | # public 110 | 111 | # vuepress build output 112 | .vuepress/dist 113 | 114 | # vuepress v2.x temp and cache directory 115 | .temp 116 | .cache 117 | 118 | # Docusaurus cache and generated files 119 | .docusaurus 120 | 121 | # Serverless directories 122 | .serverless/ 123 | 124 | # FuseBox cache 125 | .fusebox/ 126 | 127 | # DynamoDB Local files 128 | .dynamodb/ 129 | 130 | # TernJS port file 131 | .tern-port 132 | 133 | # Stores VSCode versions used for testing VSCode extensions 134 | .vscode-test 135 | 136 | # yarn v2 137 | .yarn/cache 138 | .yarn/unplugged 139 | .yarn/build-state.yml 140 | .yarn/install-state.gz 141 | .pnp.* 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 gaohongxiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web3交互系列脚本 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
模块类别模块内容功能描述
🔐 加解密模块crypt数据加密与解密功能
💱 交易所模块binance币安交易所API交互
okxOKX交易所API交互
bybitBybit交易所API交互
⛓️ 链上交互模块utxo-script比特币等UTXO模型链交互
evm-script以太坊等EVM兼容链交互
sol-scriptSolana链交互
sui-scriptSui链交互
🤖 RPA模块bitbrowserBitBrowser指纹浏览器自动化操作
chrome本地Chrome浏览器多开与控制
🌐 社交自动化模块XX自动化操作
gmailGmail自动化操作
galxeGalxe自动化操作
📢 通知模块notification消息通知与日志记录
notifier钉钉机器人提醒
🛠️ 工具库模块generatewallet多链钱包生成工具
captcha验证码自动识别与解决
otp两因素认证(2FA)工具
formatdata数据格式化与处理
其他一些细碎的辅助工具
101 | 102 | 持续完善中。。。 103 | 104 | ## 免责声明 105 | 106 | 鉴于能力有限,后续不排除有大幅度的改动,请谨慎使用。 107 | 108 | 本脚本只作为学习用途,使用风险自负! 109 | 110 | ## 准备工作 111 | 112 | #### 1、克隆并安装依赖 113 | ``` 114 | git clone https://github.com/gaohongxiang/web3-script.git 115 | cd web3-script 116 | 117 | sudo npm install -g pnpm 118 | pnpm install 119 | ``` 120 | 121 | 包管理器从npm迁移至pnpm 122 | - 支持同一依赖的多个版本并存,解决版本冲突问题 123 | - 严格的依赖树结构,杜绝幽灵依赖 124 | - 硬链接共享依赖,节省磁盘空间 125 | - 更快的并行安装速度 126 | 127 | 比如我的代码里用的ethersV6版本,而uniswap的sdk还用的ethersV5版本,那么就会产生冲突。可以如下解决 128 | 129 | > 注意:ethers两个版本必须起别名,否则行为未知。比如v6版本叫ethers,不改名为ethers-v6,uniswap还是会用v6版本,因为他默认找名字为ethers的包。 130 | 131 | ```json 132 | { 133 | "dependencies": { 134 | "ethers-v5": "npm:ethers@5.8.0", 135 | "ethers-v6": "npm:ethers@^6.13.5", 136 | "@uniswap/permit2-sdk": "1.3.0", 137 | "@uniswap/universal-router-sdk": "4.19.5", 138 | }, 139 | "pnpm": { 140 | "overrides": { 141 | "@uniswap/universal-router-sdk>ethers": "5.8.0", 142 | "@uniswap/permit2-sdk>ethers": "5.8.0" 143 | } 144 | } 145 | } 146 | ``` 147 | 查一下所有依赖包中使用的 ethers 版本。--depth=10 表示显示依赖树的深度为10层 148 | ``` 149 | pnpm list ethers --depth=10 150 | ``` 151 | 152 | 结果如下,可以看到uniswap的sdk使用ethersV5版本,而我自己的代码中通过`import { ethers } from 'ethers-v6';`导入即可用V6版本。 153 | ``` 154 | @uniswap/permit2-sdk 1.3.0 155 | └── ethers 5.8.0 156 | @uniswap/universal-router-sdk 4.19.5 157 | ├─┬ @uniswap/permit2-sdk 1.3.0 158 | │ └── ethers 5.8.0 159 | └── ethers 5.8.0 160 | ethers 5.8.0 161 | ethers 6.13.5 162 | ``` 163 | 164 | #### 2、配置文件 165 | 166 | `.env-example`是示例配置文件,实际使用中需要同目录下创建`.env`文件,内容根据实际情况修改。用到哪个模块可查看模块文档增加配置。 167 | 168 | `.filesConfig-example.json`是数据配置文件,实际使用中需要同目录下创建`.filesConfig.json`文件,内容根据实际使用到的数据文件增删。此文件是配合`utils-module/formatdata.js`来格式化数据的,将所有准备好的数据文件按照`indexId`组合到一起,方便使用。注意data目录下创建的数据文件名字需要跟此文件里的名字相同。 169 | 170 | #### 3、数据文件 171 | 172 | 数据文件全部放在`data`目录下。根据用到的模块添加相应的文件。助记词、私钥、api数据等敏感字段必须加密存储(加密方法查看`crypt-module`模块)。主要用到三种格式文件:`.csv`、`.json`、`.xlsx` 173 | 174 | ##### 代币信息文件 `token-example.json` 175 | 176 | 此文件是一些常用的token信息,实际使用中需要同目录下创建`token.json`文件。可以自行添加token。 177 | 178 | 179 | ##### 钱包文件 180 | 181 | 此类文件存放链上地址,采用`.csv`格式,如`walletBtc.csv`、`walletEth.csv`等。基本字段如下所示,根据实际情况增删。 182 | 183 | 已存在的地址可以使用`crypt-module`模块加密敏感字段。未存在的地址可以直接使用`utils-module`模块生成,敏感字段会自动加密。 184 | 185 | ``` 186 | indexId,address,enPrivateKey,enMnemonic 187 | 1,地址1,加密后的私钥,加密后的助记词 188 | 2,地址2,加密后的私钥,加密后的助记词 189 | 3,地址3,加密后的私钥,加密后的助记词 190 | ... 191 | ``` 192 | 193 | >注意:csv文件第一个字段统一为indexId,方便后续多文件组合数据,如果不加此字段,程序读取文件时会自动添加。除了indexId字段,其他字段不准起相同的名字,防止多文件合并数据时漏数据。编辑器可以安装一下`Rainbow CSV`插件,每个字段用不同颜色显示,很容易阅读。 194 | 195 | ##### 交易所文件 196 | 197 | 交易所文件分为两类 198 | - 一类是api文件,此类文件存放交易所api,用于转出。采用`.json`格式,如 `binance.json`、`okx.json`。 199 | - 一类是地址文件,此类文件存放交易所的收款地址,用于转入。采用`.csv`格式,如`addressBinance.csv`、`addressOkx.csv`。 200 | 201 | 交易所api文件,见`exchange-module/README.md` 202 | 203 | 交易所地址文件。以ok为例,主账户和5个子账户都能生成20个地址 204 | ``` 205 | indexId,okxEthAddress,okxBtcAddress,okxSolAddress,okxSuiAddress,... 206 | 1,okx的eth地址1,okx的btc地址1,okx的sol地址1,okx的sui地址1,... 207 | 2,okx的eth地址2,okx的btc地址2,okx的sol地址2,okx的sui地址2,... 208 | 3,okx的eth地址3,okx的btc地址3,okx的sol地址3,okx的sui地址3,... 209 | ``` 210 | 211 | ##### ip文件 `ip.csv` 212 | 213 | 此文件存放ip信息,自动处理成各协议需要的格式,根据需要使用。详情查看`utils-module`文档。 214 | ``` 215 | proxyIp:proxyPort:proxyUsername:proxyPassword 216 | xxxxxx:xxxxxx:xxxxxx:xxxxxx 217 | xxxxxx:xxxxxx:xxxxxx:xxxxxx 218 | ...... 219 | ``` 220 | 221 | ## 各模块使用示例 222 | 223 | 使用示例详见各模块文档。 224 | 225 | --- 226 | --- 227 | 228 | 如果觉得本脚本对您有帮助,欢迎支持,您的鼓励是我持续更新的动力!☕☕☕ 229 | 230 | ``` 231 | BTC: bc1pvw4w2kj6f97kqkfsalfk804tv60lwrx5pqlf34c595m2pggwyfysr3l4ld 232 | 233 | EVM: 0xbc7fe470be2a5a1ea8db55be44e234b0224b3198 234 | 235 | SOL: 8yJyYESPppRDb67GUzC4brCaY8UVVZU3JzBJ4DtkMc45 -------------------------------------------------------------------------------- /filesConfig-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataFiles": [ 3 | "data/wallet/walletBtc.csv", 4 | "data/wallet/walletEth.csv", 5 | "data/wallet/walletSol.csv", 6 | "data/wallet/walletSui.csv", 7 | 8 | "data/exchange/addressBinance.csv", 9 | "data/exchange/addressOkx.csv", 10 | 11 | "data/social/x.csv", 12 | "data/social/discord.csv", 13 | "data/social/email.csv", 14 | 15 | "data/ip.csv", 16 | 17 | "data/bitbrowser.xlsx" 18 | ], 19 | "fingerprintConfig": { 20 | "basePath": "$HOME/Chrome多开", 21 | "fileName": "fingerprint.json" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3-script", 3 | "version": "1.0.0", 4 | "description": "币圈交互系列脚本", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "type": "module", 10 | "bin": { 11 | "proxy-manager": "./packages/rpa-module/chrome/chromeBrowser/proxyManger.js" 12 | }, 13 | "keywords": [], 14 | "author": "gaohongxiang", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@azure/msal-node": "^3.0.1", 18 | "@faker-js/faker": "^9.5.1", 19 | "@metaplex-foundation/digital-asset-standard-api": "^1.0.4", 20 | "@metaplex-foundation/mpl-token-metadata": "^3.2.1", 21 | "@metaplex-foundation/mpl-toolbox": "^0.9.4", 22 | "@metaplex-foundation/umi": "^0.9.2", 23 | "@metaplex-foundation/umi-bundle-defaults": "^0.9.2", 24 | "@metaplex-foundation/umi-rpc-web3js": "^0.9.2", 25 | "@microsoft/microsoft-graph-client": "^3.0.7", 26 | "@mysten/sui": "^1.13.0", 27 | "@scallop-io/sui-kit": "^1.3.0", 28 | "@solana/spl-token": "^0.4.9", 29 | "@solana/web3.js": "^1.95.4", 30 | "@uniswap/permit2-sdk": "1.3.0", 31 | "@uniswap/sdk-core": "^7.7.2", 32 | "@uniswap/universal-router-sdk": "4.19.5", 33 | "@uniswap/v3-sdk": "^3.25.2", 34 | "axios": "^1.7.9", 35 | "bip32": "^5.0.0-rc.0", 36 | "bip39": "^3.1.0", 37 | "bitcoinjs-lib": "^6.1.6", 38 | "bs58": "^6.0.0", 39 | "canvas": "^3.1.0", 40 | "ccxt": "^4.4.77", 41 | "clipboardy": "^4.0.0", 42 | "csv-parse": "^5.5.6", 43 | "csv-stringify": "^6.5.1", 44 | "dotenv": "^16.4.5", 45 | "ecpair": "^2.1.0", 46 | "ethers-v5": "npm:ethers@5.8.0", 47 | "ethers-v6": "npm:ethers@^6.13.5", 48 | "express": "^4.21.2", 49 | "fingerprint-generator": "^2.1.62", 50 | "fingerprint-injector": "^2.1.62", 51 | "google-auth-library": "^9.15.0", 52 | "googleapis": "^144.0.0", 53 | "https-proxy-agent": "^7.0.6", 54 | "imap": "^0.8.19", 55 | "mailparser": "^3.7.2", 56 | "minimist": "^1.2.8", 57 | "node-pop3": "^0.9.0", 58 | "otplib": "^12.0.1", 59 | "playwright": "^1.49.1", 60 | "robotjs": "^0.6.0", 61 | "socks": "^2.8.4", 62 | "socks-proxy-agent": "^8.0.4", 63 | "tiny-secp256k1": "^2.2.3", 64 | "twitter-api-v2": "^1.20.2", 65 | "xlsx": "^0.18.5" 66 | }, 67 | "pnpm": { 68 | "overrides": { 69 | "@uniswap/universal-router-sdk>ethers": "5.8.0", 70 | "@uniswap/permit2-sdk>ethers": "5.8.0" 71 | }, 72 | "onlyBuiltDependencies": [ 73 | "bigint-buffer", 74 | "bufferutil", 75 | "canvas", 76 | "ccxt", 77 | "keccak", 78 | "robotjs", 79 | "secp256k1", 80 | "utf-8-validate" 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/chain-module/evm-script/README.md: -------------------------------------------------------------------------------- 1 | # EVM链交互脚本 2 | 3 | ## 注意事项 4 | - 本脚本支持多个RPC提供商(Infura、Alchemy、公共节点等),私有节点需要在.env文件中配置相应的API密钥 5 | - 创建钱包文件,并加密私钥、助记词数据 6 | - erc20代币信息存储在`./data/token.json`文件,需要的token信息可自行添加 7 | 8 | ## 使用示例 9 | 10 | ```js 11 | import { evmClient } from "./index.js"; 12 | 13 | // 创建客户端实例(支持自定义链配置) 14 | // 方式1: 使用现有的链和现有RPC提供商 15 | const client = await evmClient.create({ 16 | chain: 'eth', // 链名称,支持多种别名,如'eth'/'ethereum'/'erc20' 17 | rpcProvider: 'infura', // RPC提供商名称,可选值:infura、alchemy、public等(取决于链支持) 18 | enPrivateKey: '加密私钥', // 通过加密模块加密过的私钥 19 | socksProxyUrl: null, // 可选,代理URL,默认null 20 | tokenFile: './data/token.json' // 可选,代币信息文件,存储代币address、abi、decimals等,默认'./data/token.json'。根据你的数据文件位置改 21 | }); 22 | 23 | // 方式2: 使用现有的链和自定义RPC(可以不传nativeToken) 24 | const client = await evmClient.create({ 25 | chain: 'eth', 26 | customChainOptions: { 27 | rpc: 'https://your-custom-rpc-url.com', // 自定义RPC URL,优先级高于rpcProvider 28 | } 29 | ... 30 | }); 31 | 32 | // 方式3: 使用自定义链和自定义RPC 33 | const client = await evmClient.create({ 34 | chain: 'custom_chain_name', // 自定义链名称 35 | customChainOptions: { 36 | rpc: 'https://your-custom-rpc-url.com', // 使用自定义链时必须提供自定义RPC 37 | nativeToken: 'token' // 使用自定义链时必须提供原生token 38 | } 39 | ... 40 | }); 41 | 42 | 43 | 44 | // 获取地址余额 45 | const balance = await client.getBalance({ 46 | address: '0x...', // 要查询的地址 47 | token: 'ETH' // 代币名称,如 ETH/USDT/WETH 等 48 | }); 49 | 50 | // 发送代币 51 | const txHash = await client.transfer({ 52 | toAddress: '0x...', // 接收地址 53 | token: 'ETH', // 代币名称,如 ETH/USDT/WETH 等 54 | value: 0.1, // 转账数量(数字类型) 55 | gasOptions: { // [可选] gas设置 56 | multiplier: 1.1, // gas价格乘数,用于加速交易 57 | useEIP1559: true // 是否使用EIP-1559交易类型 58 | } 59 | }); 60 | 61 | // 授权代币 62 | await client.checkAndApproveToken({ 63 | token: 'USDT', // 代币名称,如 USDT/WETH 等(ETH无需授权) 64 | amount: 100, // 交易金额(数字类型) 65 | permit2Amount: 1000, // [可选] 授权给permit2的金额,应大于amount 66 | targetContract: '0x...', // 目标合约地址 67 | gasOptions: { // [可选] gas设置 68 | multiplier: 1.1, // gas价格乘数,用于加速交易 69 | useEIP1559: true // 是否使用EIP-1559交易类型 70 | } 71 | }); 72 | 73 | // 监听代币转账 74 | const contract = await client.listenContract({ 75 | listenAddress: '0x...', // 要监听的地址 76 | listenToken: 'USDT', // 要监听的代币名称 77 | direction: 'in' // 监听方向:'in'=转入,'out'=转出 78 | }); 79 | ``` 80 | 81 | ### uniswap V3 通用路由兑换代币示例 82 | ```js 83 | import { tradeClient } from "./trade.js"; 84 | 85 | // 创建客户端实例 86 | const trade = await tradeClient.create({ 87 | chain: 'eth', // 链名称 88 | rpcProvider: 'infura', // RPC提供商名称,可选值:infura、alchemy、public(公共节点)) 89 | enPrivateKey: '加密私钥', // 通过加密模块加密过的私钥 90 | socksProxyUrl: null, // 可选,代理URL,默认null 91 | tokenFile: './data/token.json' // 可选,代币信息文件,存储代币address、abi、decimals等,默认'./data/token.json'。根据你的数据文件位置改 92 | }); 93 | 94 | // 执行代币兑换(暂不支持原生代币与包装代币互换,因为直接用WETH合约就行了,不走uniswap路由) 95 | const tx = await trade.uniswapUniversalRouterV3Swap({ 96 | tokenIn: 'ETH', // 输入代币名称,如 ETH/USDT/WETH 等 97 | tokenOut: 'USDT', // 输出代币名称,如 ETH/USDT/WETH 等 98 | amountIn: 0.1, // 输入金额(数字类型,使用代币精度单位) 99 | permit2AllowanceAmount: 1, // [可选] 授权给permit2的金额,应 >= amountIn 100 | slippage: 0.5, // [可选] 滑点百分比,默认0.5% 101 | permitDeadline: 10, // [可选] 授权过期时间(分钟),默认10分钟 102 | gasOptions: { // [可选] gas设置 103 | multiplier: 1.1, // gas价格乘数,用于加速交易 104 | useEIP1559: true // 是否使用EIP-1559交易类型 105 | } 106 | }); 107 | ``` 108 | 109 | uniswap通用路由的写法比较灵活,但是需要自己组装命令,用到permit2签名。 110 | 111 | >路径:代币授权给permit2合约 -> permit2签名后将代币转移给路由合约 -> 组装命令 -> 路由合约执行兑换交易 112 | 113 | 理论上代币可以无限授权给permit2合约,然后每次permit2合约签名(有作用域、有有效期)后将代币转移给路由合约,因为签名不需要上链,所以就省了一笔gas费。但是正因为签名不上链,有些无感,如果被骗permit2签名,那么签名的资金就会被盗。所以最好代币不要无限授权给permit2合约,可以设置一个相对够用的量,这些量内只需要一笔gas就可以完成交易,相对也安全。兼顾效率和安全。 114 | 115 | 如有报错无法找到原因可以去`https://dashboard.tenderly.co/explorer/simulations`模拟交易,需要路由地址+交易数据(错误信息里有)。Tenderly是一个专门面向Web3开发者的开发、监控和测试平台。 116 | 117 | Uniswap各个SDK的主要作用 118 | - sdk-core:最基础的SDK,提供核心功能。包含Token、Price、Route等基础类,其他SDK都依赖于这个核心SDK 119 | - v2-sdk:Uniswap V2协议的SDK。处理V2的配对交易、流动性添加/移除等,适用于想使用V2协议的开发者 120 | - v3-sdk:Uniswap V3协议的SDK。处理V3的集中流动性、多费率池等特性,提供更复杂的定价和流动性管理功能 121 | - v4-sdk:Uniswap V4协议的SDK(新版本)。处理V4的新特性,目前还在开发中 122 | - permit2-sdk:处理代币授权的SDK。实现EIP-2612标准的permit功能,允许用户用签名而不是交易来授权代币 123 | - universal-router-sdk:统一的路由SDK。可以同时处理V2、V3的交易,支持跨协议的最优路径查找 124 | 125 | 参考 126 | - uniswap在各链部署的合约:https://docs.uniswap.org/contracts/v3/reference/deployments/ 127 | - uniswap通用路由各命令:https://docs.uniswap.org/contracts/universal-router/technical-reference#transfer 128 | - permit2文档:https://docs.uniswap.org/contracts/permit2/overview 129 | - uniswap报价:https://docs.uniswap.org/sdk/v3/guides/swaps/quoting、https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/libs/quote.ts 130 | - uniswapV3单路径交易示例:https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps、https://www.quicknode.com/guides/defi/dexs/how-to-swap-tokens-on-uniswap-v3 131 | - uniswap各sdk:https://docs.uniswap.org/sdk/v3/guides/web3-development-basics#the-uniswap-development-suite 132 | - 参考示例: 133 | - https://github.com/saad-s/uniswap-uni-router-permit2/blob/main/src/index.js 134 | - https://github.com/Uniswap/universal-router/blob/main/test/integration-tests/UniversalRouter.test.ts 135 | - https://github.com/0xMaka/w3py/blob/main/uniswapv3/v3exactOutput.py 136 | - https://github.com/CodeWithJoe2020/UniswapUniversalRouter/blob/main/sell_token_ur.py 137 | 138 | WTF-Ethers教程 139 | - https://github.com/WTFAcademy/WTF-Ethers 140 | 141 | 调试网站 142 | - https://dashboard.tenderly.co/ 143 | 144 | -------------------------------------------------------------------------------- /packages/chain-module/sol-script/README.md: -------------------------------------------------------------------------------- 1 | # sol脚本 2 | 3 | ## 注意事项 4 | - 本脚本使用heliusKey、quickNodeKey的rpc服务,需要自己申请,将apiKey配置到.env文件 5 | - 创建钱包文件,并加密私钥、助记词数据 6 | - spl代币信息存储在`./data/token.json`文件,需要的token信息可自行添加 7 | 8 | ## 示例 9 | ``` 10 | // 路径根据文件的位置自行调整 11 | import { getBalance as solanaGetBalance, transfer as solanaTransfer } from './packages/sol-script/index.js' 12 | 13 | // 获取地址余额 14 | // 参数:{ address, token='SOL', tokenFile = './data/token.json' } 15 | // const balance = await solanaGetBalance({ address: 'HgkB9gJH58zxauqwLqgGVgoHH5FWNdiiVrUsXjTFVukx', token: 'sol', tokenFile = './data/token.json' }) 16 | 17 | // 几个数据就是发送给几个 18 | const toData = [ 19 | ['HgkB9gJH58zxauqwLqgGVgoHH5FWNdiiVrUsXjTFVukx', 0.01], 20 | ['Fv5rwEsDoWfqC7xn6QPxZtoz73YN563rRAtCSyuX2xxy', 0.01], 21 | ] 22 | 23 | // 发送代币 24 | // 参数:{ enPrivateKey, toData, token, tokenFile='./data/token.json' } 25 | solanaTransfer({ enPrivateKey: '加密的私钥', toData, token:'sol', tokenFile:'./data/token.json' }) 26 | ``` 27 | 28 | ## 参考 29 | 30 | - 官方库:https://github.com/solana-labs/solana-web3.js/tree/master 31 | - 官方交易示例:https://github.com/solana-labs/solana-web3.js/blob/30f9254a9c67313f82b7bdd03f73b7543e78fc1b/examples/transfer-lamports/src/example.ts#L140 32 | - quickNode solana 文档:https://www.quicknode.com/guides/solana-development/getting-started/solana-fundamentals-reference-guide 33 | - 崔棉大师 Solana 文档:https://www.solana-cn.com/SolanaDocumention/clients/javascript-reference.html 34 | - solana中文开发课程:https://www.solanazh.com/ 35 | - solana中文开发课程:https://decert.me/tutorial/sol-dev/ 36 | - 如何自己写一个pump.fun狙击枪:https://chainbuff.com/d/12 37 | - 笨方法学 Solana 合约交互(gm365):https://x.com/gm365/status/1797502378230603962 38 | - Solana Web3.js 2.0:Solana 开发的新篇章:https://blog.quicknode.com/solana-web3-js-2-0-a-new-chapter-in-solana-development/ 39 | 40 | 41 | 社区 42 | 43 | - https://soldev.cn/topics/node1 44 | - https://t.me/solanadevcamp -------------------------------------------------------------------------------- /packages/chain-module/sol-script/umi.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import bs58 from 'bs58'; 3 | 4 | import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'; 5 | import { 6 | createSignerFromKeypair, 7 | signerIdentity, 8 | publicKey, 9 | transactionBuilder, 10 | sol, 11 | } from '@metaplex-foundation/umi'; 12 | import { 13 | TokenStandard, 14 | mplTokenMetadata, 15 | fetchDigitalAsset, 16 | findTokenRecordPda, 17 | transferV1, 18 | } from '@metaplex-foundation/mpl-token-metadata'; 19 | import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox'; 20 | import { dasApi } from '@metaplex-foundation/digital-asset-standard-api'; 21 | 22 | import { deCryptText } from '../../crypt-module/crypt.js'; 23 | 24 | /** 25 | * 创建 Umi 实例并设置签名者。 26 | * 27 | * 该函数初始化 Umi 实例,连接到可用的 RPC 节点,并设置签名者身份。 28 | * 它会尝试连接多个 RPC 提供者,直到找到一个可用的连接。 29 | * 30 | * @param {string} enPrivateKey - 加密的私钥 31 | * @returns {Promise} 返回包含 umi 实例和签名者的对象 32 | * @throws {Error} 如果所有 RPC 提供者都无法连接,则抛出错误 33 | */ 34 | async function createUmiAndSigner(enPrivateKey) { 35 | // 定义 RPC 提供者 36 | const rpcProviders = [ 37 | { url: `https://mainnet.helius-rpc.com/?api-key=${process.env.heliusKey}`, name: 'Helius' }, 38 | { url: `https://snowy-shy-hill.solana-mainnet.quiknode.pro/${process.env.quickNodeKey}`, name: 'QuickNode' } 39 | ]; 40 | let umi; 41 | for (const provider of rpcProviders) { 42 | try { 43 | umi = createUmi(provider.url) 44 | .use(mplTokenMetadata()) 45 | .use(dasApi()); 46 | 47 | // 测试连接 48 | await umi.rpc.getLatestBlockhash(); 49 | console.log(`成功连接到 ${provider.name} RPC`); 50 | break; 51 | } catch (error) { 52 | console.warn(`无法连接到 ${provider.name} RPC:`, error.message); 53 | if (provider === rpcProviders[rpcProviders.length - 1]) { 54 | throw new Error('所有 RPC 提供者都无法连接'); 55 | } 56 | } 57 | } 58 | 59 | const privateKey = await deCryptText(enPrivateKey); 60 | const keypair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey)); 61 | const signer = createSignerFromKeypair(umi, keypair); 62 | umi.use(signerIdentity(signer)); 63 | return { umi, signer }; 64 | } 65 | 66 | /** 67 | * 转移 NFT 到指定地址。 68 | * 69 | * 该函数处理 NFT 的转移操作,支持标准 NFT 和可编程 NFT (pNFT)。 70 | * 它会根据 NFT 的名称查找对应的资产,并处理转移所需的所有步骤。 71 | * 72 | * @param {Object} params - 函数参数对象 73 | * @param {string} params.enPrivateKey - 加密的发送方私钥 74 | * @param {string} params.toAddress - 接收方地址 75 | * @param {string} params.nftName - 要转移的 NFT 名称 76 | * @returns {Promise} 返回交易签名 77 | * @throws {Error} 如果找不到指定的 NFT 或转移过程中出现错误 78 | * 79 | * 主要步骤: 80 | * 1. 创建 Umi 实例和签名者 81 | * 2. 根据 NFT 名称查找对应资产 82 | * 3. 获取 NFT 详细信息和代币标准 83 | * 4. 查找源和目标关联令牌账户 84 | * 5. 根据 NFT 类型(标准/可编程)准备转移参数 85 | * 6. 创建并发送转移交易 86 | */ 87 | export async function transferNft({ enPrivateKey, toAddress, nftName }) { 88 | // 创建 Umi 实例和签名者 89 | const { umi, signer } = await createUmiAndSigner(enPrivateKey); 90 | const fromPublicKey = signer.publicKey; 91 | const toPublicKey = publicKey(toAddress); 92 | 93 | console.log(`尝试从 ${fromPublicKey} 向 ${toPublicKey} 转账 NFT: ${nftName}`); 94 | 95 | // 查找指定名称的 NFT 96 | const assets = await umi.rpc.getAssetsByOwner({ owner: fromPublicKey }); 97 | const matchingNft = assets.items.find(asset => asset.content.metadata.name === nftName); 98 | 99 | if (!matchingNft) { 100 | throw new Error(`NFT with name "${nftName}" not found in the wallet`); 101 | } 102 | 103 | const mint = matchingNft.id; 104 | console.log(`找到 NFT, Mint 地址: ${mint}`); 105 | 106 | // 获取 NFT 详细信息和代币标准 107 | const asset = await fetchDigitalAsset(umi, mint); 108 | const tokenStandard = asset.metadata.tokenStandard.value; 109 | 110 | // 查找源和目标关联令牌账户 111 | const sourceToken = findAssociatedTokenPda(umi, { mint, owner: fromPublicKey }); 112 | const destinationToken = findAssociatedTokenPda(umi, { mint, owner: toPublicKey }); 113 | const tokenAccount = await umi.rpc.getAccount(destinationToken); 114 | 115 | // 准备基本转移参数 116 | const transferParams = { 117 | mint, 118 | authority: signer, 119 | tokenOwner: fromPublicKey, 120 | destinationOwner: toPublicKey, 121 | sourceToken, 122 | destinationToken, 123 | tokenStandard, 124 | }; 125 | 126 | // 如果是可编程 NFT,添加额外的转移参数 127 | if (tokenStandard === TokenStandard.ProgrammableNonFungible) { 128 | transferParams.sourceTokenRecord = findTokenRecordPda(umi, { mint, token: sourceToken }); 129 | transferParams.destinationTokenRecord = findTokenRecordPda(umi, { mint, token: destinationToken }); 130 | transferParams.authorizationRules = null; // 如果有授权规则,这里需要修改 131 | } 132 | 133 | // 创建转移指令并构建交易 134 | const transferInstruction = transferV1(umi, transferParams); 135 | const latestBlockhash = await umi.rpc.getLatestBlockhash(); 136 | const tx = transactionBuilder() 137 | .add(transferInstruction) 138 | .setFeePayer(signer) 139 | .setBlockhash(latestBlockhash); 140 | 141 | // 发送交易并等待确认 142 | try { 143 | const result = await tx.sendAndConfirm(umi); 144 | const signature = bs58.encode(result.signature); 145 | console.log(`NFT 转移成功! 交易哈希: ${signature}`); 146 | return signature; 147 | } catch (error) { 148 | console.error("NFT 转移过程中发生错误:", error); 149 | throw error; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /packages/chain-module/sui-script/README.md: -------------------------------------------------------------------------------- 1 | # sui脚本 2 | 3 | Sui TypeScript SDK 有两个版本,一个是官方版,一个是社区版。社区版使用比较简单 4 | 5 | ## 注意事项 6 | - 创建钱包文件,并加密私钥、助记词数据 7 | - sui代币信息存储在`./data/token.json`文件,需要的token信息可自行添加 8 | 9 | ``` 10 | const toData = [ 11 | ['0xfe91445a41fbbce5d5b278cd89d6c2081f0a6697148296ce5f9ffd5155f223e6', 0.01], 12 | ]; 13 | 14 | // { address, token='SUI', tokenFile = './data/token.json' } 15 | // getBalance({ address:'0xfe91445a41fbbce5d5b278cd89d6c2081f0a6697148296ce5f9ffd5155f223e6', token:'usdc', tokenFile : './data/token.json' }) 16 | 17 | // { enPrivateKey, toData, token = "SUI", tokenFile = './data/token.json' } 18 | transfer({ enPrivateKey:'加密的私钥', toData, token:"usdc", tokenFile:'./data/token.json' }) 19 | ``` 20 | 21 | ## 参考 22 | - Sui TypeScript SDK 快速入门:https://sdk.mystenlabs.com/typescript 23 | - Sui TypeScript SDK 文档:https://sdk.mystenlabs.com/typescript/transaction-building/basics 24 | - 官方 TypeScript SDK:https://github.com/MystenLabs/sui/tree/main/sdk/typescript 25 | - 社区 TypeScript SDK:https://github.com/scallop-io/sui-kit 26 | - 如何合并和转移 2k sui coin 对象 #18254:https://github.com/MystenLabs/sui/discussions/18254?sort=new 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/chain-module/sui-script/suiKit.js: -------------------------------------------------------------------------------- 1 | import { SuiKit } from '@scallop-io/sui-kit'; 2 | import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; 3 | import { deCryptText } from '../../crypt-module/crypt.js'; 4 | import { getTokenInfo } from '../../utils-module/utils.js'; 5 | 6 | const rpc = [ 7 | 'https://fullnode.mainnet.sui.io:443', 8 | 'https://mainnet.suiet.app', 9 | 'https://rpc-mainnet.suiscan.xyz', 10 | 'https://mainnet.sui.rpcpool.com', 11 | 'https://sui-mainnet.nodeinfra.com', 12 | 'https://mainnet-rpc.sui.chainbase.online', 13 | 'https://sui-mainnet-ca-1.cosmostation.io', 14 | 'https://sui-mainnet-ca-2.cosmostation.io', 15 | 'https://sui-mainnet-us-1.cosmostation.io', 16 | 'https://sui-mainnet-us-2.cosmostation.io', 17 | ] 18 | 19 | export async function getBalance({ address, token = 'SUI', tokenFile = './data/token.json' }) { 20 | try { 21 | token = token.toUpperCase(); 22 | const tokenInfo = getTokenInfo({ token, chain: 'sui', tokenFile }); 23 | if (!tokenInfo) { console.log('没有此代币信息,请先添加'); return }; 24 | const { coinType, decimals: coinDecimals } = tokenInfo; 25 | const client = new SuiClient({ url: getFullnodeUrl('mainnet') }); 26 | const response = await client.getBalance({ owner: address, coinType }); 27 | const balance = response.totalBalance / 10 ** coinDecimals; 28 | const coinObjectCount = response.coinObjectCount; 29 | console.log(`地址 ${address} ${token} 余额: ${balance}, 包含 ${coinObjectCount} 个对象。`); 30 | return balance; 31 | } catch (error) { throw error; } 32 | } 33 | 34 | // // suiKit库查询余额用的是私钥,不太方便。该用官方库实现方式 35 | // export async function getBalance({ enPrivateKey, token = "SUI", tokenFile = './data/token.json' }) { 36 | // try { 37 | // token = token.toUpperCase(); 38 | // const secretKey = await deCryptText(enPrivateKey); 39 | // const suiKit = new SuiKit({ secretKey, fullnodeUrls: rpc }); 40 | 41 | // const tokenInfo = getTokenInfo({token, chain:'sui', tokenFile}); 42 | // if (!tokenInfo) { console.log('没有此代币信息,请先添加'); return }; 43 | // const { coinType, decimals: coinDecimals } = tokenInfo; 44 | // const address = await suiKit.getAddress(secretKey); 45 | 46 | // const response = await suiKit.getBalance(coinType, secretKey); 47 | // const balance = response.totalBalance / 10 ** coinDecimals; 48 | // const coinObjectCount = response.coinObjectCount; 49 | // console.log(`地址 ${address} ${token} 余额: ${balance}, 包含 ${coinObjectCount} 个对象。`); 50 | // } catch (error) { throw error; } 51 | // } 52 | 53 | export async function transfer({ enPrivateKey, toData, token = "SUI", tokenFile = './data/token.json' }) { 54 | try { 55 | token = token.toUpperCase(); 56 | const tokenInfo = getTokenInfo({ token, chain: 'sui', tokenFile }); 57 | if (!tokenInfo) { console.log('没有此代币信息,请先添加'); return }; 58 | const { coinType, decimals: coinDecimals } = tokenInfo; 59 | 60 | // 分解成两个数组 61 | const addresses = toData.map(item => item[0]); // 提取地址 62 | let amounts = toData.map(item => item[1]); // 提取数量 63 | amounts = amounts.map(amount => amount * 10 ** coinDecimals); 64 | 65 | const secretKey = await deCryptText(enPrivateKey); 66 | const suiKit = new SuiKit({ secretKey, fullnodeUrls: rpc }); 67 | 68 | if (token === "SUI") { 69 | const response = await suiKit.transferSuiToMany(addresses, amounts); 70 | console.log(`交易成功, 交易哈希: ${response.digest}`); 71 | } else { 72 | const response = await suiKit.transferCoinToMany(addresses, amounts, coinType); 73 | console.log(`交易成功, 交易哈希: ${response.digest}`); 74 | } 75 | } catch (error) { throw error; } 76 | } 77 | -------------------------------------------------------------------------------- /packages/chain-module/utxo-script/README.md: -------------------------------------------------------------------------------- 1 | # utxo系交互脚本 2 | 3 | ## 注意事项 4 | 5 | - 本脚本目前支持`P2TR类型 bc1p开头`、`P2WPKH类型 bc1q开头`的地址 6 | - 本脚本目前只支持`CPFP`类型加速交易 7 | - 本脚本目前支持传递`助记词`或`WIF`类型私钥。使用私钥时地址类型需与`scriptType`类型一致 8 | - 创建钱包文件,并加密私钥、助记词数据 9 | 10 | #### 2、使用示例 11 | 12 | ``` 13 | import { getAddressUTXOs, getBalance as getBtcBalance, transfer as utxoTransfer, collect as utxoCollect, speedUp, splitUTXO } from './index.js'; 14 | 15 | // 获取地址余额 16 | // 参数: { address, chain = 'btc' } 17 | // await getBtcBalance({address:'地址', chain:'fb'}); 18 | 19 | 20 | // 发送交易(引申:如果toData中有多个相同地址,即是拆分utxo) 21 | // 参数:{ enBtcMnemonicOrWif, toData, chain = 'btc', filterMinUTXOSize = 10000, scriptType='P2TR'(bc1p) | P2WPKH(bc1q) | P2PKH(1), GasSpeed = 'high', highGasRate = 1.1 } 22 | const toData = [['bc1pn64509kwl......nn9g4hj8cleg', 0.01], ['bc1pas49g......vhpwtz04fy3p', 0.01]]; 23 | // await utxoTransfer({ enBtcMnemonicOrWif: d['enBtcMnemonic'], toData, chain: 'fractal', filterMinUTXOSize: 10000, scriptType:'p2tr' }) 24 | 25 | // 归集(将每个地址大于filterMinUTXOSize的utxo全部归集) 26 | // 参数 { fromData, toAddress, chain = 'btc', filterMinUTXOSize = 10000, GasSpeed = 'high', highGasRate = 1.1 } 27 | const fromData = ['加密助记词或WIF私钥1', '加密助记词或WIF私钥2']; // 都是P2TR类型 28 | const fromData = [['加密助记词或WIF私钥1', 'P2TR'], ['加密助记词或WIF私钥2', 'P2WPKH']]; // 指定类型 29 | // await utxoCollect({ fromData, toAddress: 'bc1pmle2uwj......u34zxhkcd3', chain: 'btc', filterMinUTXOSize: 10, GasSpeed: 'high', highGasRate: 1 }); 30 | 31 | // 加速交易 32 | // 参数:{ enBtcMnemonicOrWif, txid, chain = 'btc', filterMinUTXOSize = 10000, scriptType='P2TR'(bc1p) | P2WPKH(bc1q) | P2PKH(1), GasSpeed='high', highGasRate=1.1 } 33 | // await speedUp({ enBtcMnemonicOrWif: d['enBtcMnemonic'], chain: 'fractal', txid: 'c9476ad42d1b7......ee12b71224f5', filterMinUTXOSize: 1000, scriptType:'p2tr' }); 34 | 35 | // 获取地址utxo 36 | // 参数:{ address, chain = 'btc', filterMinUTXOSize = 0 } 37 | // const { allUTXOs, filteredUTXOs, unconfirmedUTXOs } = await getAddressUTXOs({address: d['btcAddress'], chain:'fb', filterMinUTXOSize: 10000 }); 38 | // console.log(`地址 ${d['btcAddress']} 所有utxos: ${JSON.stringify(allUTXOs)}`); 39 | // console.log(`地址 ${d['btcAddress']} 过滤聪后utxos: ${JSON.stringify(filteredUTXOs)}`); 40 | // console.log(`地址 ${d['btcAddress']} 未确认utxos: ${JSON.stringify(unconfirmedUTXOs)}`); 41 | 42 | // 拆分utxo 43 | // 参数:{ enBtcMnemonicOrWif, chain = 'btc', filterMinUTXOSize = 10000, splitNum = 3, scriptType='P2TR'(bc1p) | P2WPKH(bc1q) | P2PKH(1), GasSpeed='high', highGasRate=1.1 } 44 | // await splitUTXO({ enBtcMnemonicOrWif: d['enBtcMnemonic'], chain: 'fractal', filterMinUTXOSize: 10000, splitNum: 2, scriptType:'p2tr' }); 45 | ``` 46 | 47 | ## 3、基础知识 48 | 49 | #### 地址类型 50 | 51 | - "m/86'/0'/0'/0/0" ---P2TR(bc1p开头) 52 | - "m/84'/0'/0'/0/0" ---P2WPKH(bc1q开头) 53 | - "m/49'/0'/0'/0/0" ---P2SH(3开头) 54 | - "m/44'/0'/0'/0/0" ---P2PKH(1开头) 55 | 56 | 路径介绍 57 | - m:根密钥。 58 | - 44':BIP44 标准,用于多币种和多账户管理。 59 | - 0':比特币主网的币种类型。 60 | - 0':第一个账户(通常用于接收来自外部的比特币)。 61 | - 0:外部链,用于生成接收地址。 62 | - 0:该外部链上的第一个地址。 63 | 64 | #### 交易基础 65 | 66 | 预估交易大小:https://bitcoinops.org/en/tools/calc-size/ 67 | 68 | 比特币技术基础(包括公私钥、地址、签名、交易结构等基础知识):https://docs.mvclabs.io/zh-CN/docs/category/basic-bitcoin-concepts 69 | 70 | 71 | #### 加速 72 | 73 | 加速分为 RBF 和 CPFP 两种方式。 74 | 75 | RBF(Replace-By-Fee)是一种比特币交易的替换机制,允许用户在交易未被确认的情况下,通过支付更高的交易费用来替换原有交易。这种机制旨在提高交易的确认速度,尤其是在网络拥堵时。 76 | 77 | CPFP(子支付父交易) 的基本思想是创建一个新的交易(子交易),它使用未确认的交易(父交易)的输出,并附带更高的手续费。这样,矿工在将子交易打包到区块时,也会打包父交易,因为子交易依赖于父交易的确认。 78 | 79 | 优缺点比较 80 | 81 | - RBF是新的高gas交易会替换旧的低gas交易,只有一笔交易,更省钱,但是需要开启RBF,只能交易发送方能加速交易。 82 | - CPFP是发送一笔新的高gas子交易,让父交易gas提升,有两笔交易,相对费钱。但是发送方和接收方都可以加速交易。 83 | 84 | #### 工具 85 | 86 | - 铭文铭刻工具:https://ordinals.ybot.io/# 87 | - 加速服务:https://mct.xyz/fractal/speedup 88 | 89 | ## 参考 90 | 91 | - https://github.com/ByteJason/BTC-Script -------------------------------------------------------------------------------- /packages/crypt-module/README.md: -------------------------------------------------------------------------------- 1 | # 加解密模块 2 | 3 | 本模块通过结合1password,用于加解密文本。之所以采用1password,是因为加解密时可以用指纹操作,解锁1password,获取存储的密码,然后执行加解密操作,避免了每次手输密码的繁琐。兼顾安全和便捷。 4 | 5 | 加解密使用`crypto`库的`aes-256-gcm`算法,使用随机初始化向量,确保数据在传输或存储过程中的唯一性、保密性和完整性。此模式提供了高效的认证和加密,因此被认为是最好的加密模式之一。 6 | 7 | > 1password中的密码切不可泄露,否则等于没加密! 8 | 9 | ### 1password 10 | 11 | 1、默认你已经熟悉使用1password密码管理器了,客户端需要勾选`设置->开发者->与1Password CLI 集成`选项。如果没有1password,可在此处了解:https://1password.com 12 | 13 | 2、创建一个密码,复制路径。 14 | 15 | 3、将路径添加到`.env`配置文件中 `personalToken = 'op://路径` 16 | 17 | ### 使用示例 18 | 19 | 1、加解密文本 20 | 21 | ``` 22 | import { enCryptText, deCryptText, enCryptColumn, deCryptColumn } from './packages/crypt-module/crypt.js'; 23 | 24 | const text = 'hello web3'; 25 | // 加密(相同文本每次加密结果也不同) 26 | const enText = await enCryptText(text); 27 | console.log(enText); 28 | //解密 29 | const text = await deCryptText(enText); 30 | console.log(text); 31 | ``` 32 | 33 | 2、加密某列文本 34 | 35 | 假设有一个`wallet.csv`文件,存放地址、私钥等信息,很显然,私钥不能明文存储。这个时候就需要给私钥这一列数据加密 36 | ``` 37 | id,address,enPrivateKey 38 | 1,bc1p0xlw9r7f63m5k4q8z8v49q35t9q0,L1k3wqhiuguv1Ki3pLnuiybr0vm 39 | 2,bc1p34x5y9x9q6u9w57h8j9z53l8z7xg,Pkmnuhfh7hbidcuin8877g2b1ns 40 | 3,bc1p34x5y9x9q6u9w57h8j9z53l8z7xg,Pkmnuhfh7hbidcuin8877g2b1ns 41 | ... 42 | ``` 43 | 44 | 执行加密操作 45 | ``` 46 | await enCryptColumn('./crypt_module/wallet.csv', 'enPrivateKey'); 47 | ``` 48 | 49 | 加密过后,`wallet.csv`文件内容如下。 50 | ``` 51 | id,address,enPrivateKey 52 | 1,bc1p0xlw9r7f63m5k4q8z8v49q35t9q0,6f74035f8943b525741079695031c2aad826a013e4534dd132fa3852a72ec91bb37156e4a2775792ba1bc66186546bb5f188618614b858 53 | 2,bc1p34x5y9x9q6u9w57h8j9z53l8z7xg,a7002ad8dfd7451fe1f53579b87900917a901e84215c579534814e36f749d7dfaff4d9550a7bbde77832a7db4bc0fae7cb53db1b5ebf1c 54 | 3,bc1p34x5y9x9q6u9w57h8j9z53l8z7xg,4eba6f49beeed4ac67fc41e60072a887e1ebfc4044618d84ad09e6d558bcc11fdb11025bc488b070979730bf6fa4635bb7e36fc0903fc0 55 | ... 56 | ``` 57 | 58 | >tips:通过第2、3条enPrivateKey数据可知,就算相同的数据加密出来也是不一样的,提高安全性。 59 | 60 | 目前大的应用场景就是使用`enCryptColumn`批量加密钱包文件的私钥、助记词等字段,安全存储。使用的时候用到哪个就用`deCryptText`解密。 61 | -------------------------------------------------------------------------------- /packages/crypt-module/crypt.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import 'dotenv/config'; 3 | import crypto from 'crypto'; 4 | import { promisify } from 'util'; 5 | import { parse } from 'csv-parse'; 6 | import { stringify } from 'csv-stringify'; 7 | import { parseToken } from './onepassword.js'; 8 | 9 | /** 10 | * 使用crypto的aes-256-gcm算法(等同于python的cryptography库的AESGCM模式)对敏感数据加解密,确保数据在传输或存储过程中的保密性和完整性。此模式提供了高效的认证和加密, 因此被认为是最好的加密模式之一。 11 | * 需要三个参数 12 | * 1、密钥 13 | * crypto.createHash('sha256') 哈希函数将密码转换成一个固定长度的 32 字节(256 位)哈希值。这使得我们可以用一个恒定长度的密钥来执行加密和解密操作。 14 | * 15 | * 2、初始化向量iv 16 | * iv的主要目的是提高加密过程的随机性,使用不同的iv,确保每个加密操作的输出都是唯一的。因此相同的明文也会加密成不同的密文。可防止对加密消息进行重放攻击。 17 | * 一个加密文本需要一个iv,一个比较好的实践是iv跟密文一起存储,解密时分离。将iv与密文一起存储不会影响加密的安全性。因为iv的主要目的是提高加密过程的随机性,而不是保持机密。因此,即使攻击者知道iv,他们仍然无法破解加密数据,除非他们获得了密钥。 18 | * 19 | * 3、authTag 20 | * 加密过程中自动生成authTag。需要存储到密文中,解密用。一个完整的密文是:iv + encrypted + authTag 21 | * 解密时拆开,authTag来验证数据的完整性。 22 | */ 23 | 24 | const tokenPath = process.env.personalToken; 25 | 26 | /** 27 | * 使用 AES-256-GCM 对字符串进行加密 28 | * @param {string} text 需要加密的文本 29 | * @returns {string} 返回加密后的密文 30 | */ 31 | export async function enCryptText(text) { 32 | try { 33 | // 如果 KEY 还没有设置,获取 KEY 密钥 34 | if(!process.env.KEY) { 35 | process.env.KEY = await parseToken(tokenPath); 36 | } 37 | // 使用密钥字符串获取密钥字节数组 38 | const passwordBytes = crypto.createHash('sha256').update(process.env.KEY).digest(); 39 | // 生成随机的 12 字节 IV 40 | const iv = crypto.randomBytes(12); 41 | // 使用密钥和 IV 创建加密器 42 | const cipher = crypto.createCipheriv('aes-256-gcm',passwordBytes, iv); 43 | // 更新加密内容,转换为 Hex 字符串 44 | let encrypted = cipher.update(text, 'utf8','hex'); 45 | // 结束加密,获取最终结果 46 | encrypted += cipher.final('hex'); 47 | // 获取 authTag 48 | const authTag = cipher.getAuthTag(); 49 | // 拼接密文格式: IV + 密文 + authTag 50 | const encryptedData = `${iv.toString('hex')}${encrypted}${authTag.toString('hex')}`; 51 | // 返回密文字符串 52 | return encryptedData; 53 | } catch(error) { 54 | console.log('加密失败', error) 55 | return null 56 | } 57 | } 58 | 59 | /** 60 | * 使用 AES-256-GCM算法对密文进行解密 61 | * @param {string} encryptedText 密文 62 | * @returns {string} 明文 63 | */ 64 | export async function deCryptText(encryptedText) { 65 | try { 66 | // 获取密钥 67 | if(!process.env.KEY) { 68 | process.env.KEY = await parseToken(tokenPath); 69 | } 70 | // 使用密钥字符串获取密钥字节数组 71 | const passwordBytes = crypto.createHash('sha256').update(process.env.KEY).digest(); 72 | // 提取 IV 73 | const iv = Buffer.from(encryptedText.slice(0, 24), 'hex'); 74 | // 提取密文 75 | const encrypted = encryptedText.slice(24, -32); 76 | // 提取 authTag 77 | const authTag = Buffer.from(encryptedText.slice(-32), 'hex'); 78 | // 创建解密器,使用 IV 和密钥 79 | const decipher = crypto.createDecipheriv('aes-256-gcm', passwordBytes, iv); 80 | // 设置 authTag ,用于验证密文的完整性 81 | decipher.setAuthTag(authTag); 82 | // 执行解密操作 83 | let decrypted = decipher.update(encrypted, 'hex', 'utf8'); 84 | decrypted += decipher.final('utf8'); 85 | // 返回解密后的明文 86 | return decrypted; 87 | } catch(error) { 88 | console.log('解密失败', error) 89 | return null 90 | } 91 | } 92 | 93 | /** 94 | * 对 CSV 文件的某一列加密。 95 | * 96 | * @param {string} filePath - CSV 文件的路径。 97 | * @param {string} columnName - 要加密的列名。 98 | */ 99 | export async function enCryptColumn(filePath, columnName) { 100 | // 读取文件并解析 CSV 数据 101 | const fileStream = fs.createReadStream(filePath); 102 | const parser = fileStream.pipe(parse({ 103 | columns: true, // 第一行为列名 104 | skip_empty_lines: true // 跳过空行 105 | })); 106 | 107 | // 对指定列进行加密 108 | const results = []; 109 | for await (const row of parser) { 110 | row[columnName] = await enCryptText(row[columnName]); // 加密指定列 111 | results.push(row); // 将处理后的行推入结果数组 112 | } 113 | 114 | // 将 stringify 转换为 Promise 风格 115 | const stringifyAsync = promisify(stringify); 116 | // 将结果数组转换为 CSV 格式 117 | const csvContent = await stringifyAsync(results, { header: true }); 118 | // 将处理后的数据保存回原文件 119 | await fs.promises.writeFile(filePath, csvContent); 120 | } 121 | 122 | /** 123 | * 对 CSV 文件的某一列解密。 124 | * 125 | * @param {string} filePath - CSV 文件的路径。 126 | * @param {string} columnName - 要解密的列名。 127 | */ 128 | export async function deCryptColumn(filePath, columnName) { 129 | // 读取文件并解析 CSV 数据 130 | const fileStream = fs.createReadStream(filePath); 131 | const parser = fileStream.pipe(parse({ 132 | columns: true, // 第一行为列名 133 | skip_empty_lines: true // 跳过空行 134 | })); 135 | 136 | // 对指定列进行解密 137 | const results = []; 138 | for await (const row of parser) { 139 | row[columnName] = await deCryptText(row[columnName]); // 加密指定列 140 | results.push(row); // 将处理后的行推入结果数组 141 | } 142 | 143 | // 将 stringify 转换为 Promise 风格 144 | const stringifyAsync = promisify(stringify); 145 | // 将结果数组转换为 CSV 格式 146 | const csvContent = await stringifyAsync(results, { header: true }); 147 | // 将处理后的数据保存回原文件 148 | await fs.promises.writeFile(filePath, csvContent); 149 | } -------------------------------------------------------------------------------- /packages/crypt-module/onepassword.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { promisify } from 'util'; 3 | import { exec } from "child_process"; 4 | 5 | const execAsync = promisify(exec); // 将 exec 转换为 Promise 风格 6 | 7 | /** 8 | * 从 1Password 中读取指定路径的令牌。 9 | * @param {string} tokenPath - 令牌在 1Password 中的路径。 10 | * @returns {Promise} - 返回解析后的令牌。 11 | * @throws {Error} - 如果读取令牌时发生错误,将抛出包含错误信息的异常。 12 | */ 13 | export async function parseToken(tokenPath) { 14 | try { 15 | const opRead = `op read ${tokenPath}`; // 构建命令以读取令牌 16 | const { stdout, stderr } = await execAsync(opRead); // 执行命令并等待结果 17 | if (stderr) { throw new Error(`1Password CLI error: ${stderr}`); } // 抛出包含错误信息的异常 18 | const token = stdout.trim(); // 去除令牌两端的空白字符 19 | return token; // 返回解析后的令牌 20 | } catch (error) { 21 | throw new Error(`Error: ${error.message}`); // 捕获并抛出错误 22 | } 23 | } 24 | 25 | /** 26 | * 解析指定文件并将内容注入到 1Password CLI。 27 | * @param {string} file - 要读取的文件路径。 28 | * @returns {Promise} - 返回解析后的数据对象。 29 | * @throws {Error} - 如果读取文件或执行命令时发生错误,将抛出包含错误信息的异常。 30 | */ 31 | export async function parseFile(file) { 32 | try { 33 | const template = await fs.promises.readFile(file, 'utf8'); // 读取文件内容 34 | const opInject = `op inject`; // 构建命令以注入内容 35 | 36 | // 使用 execAsync 执行命令并将模板内容作为输入 37 | const { stdout, stderr } = await execAsync(opInject, { input: template }); // 将 template 作为输入 38 | if (stderr) { throw new Error(`1Password CLI error: ${stderr}`); } // 抛出包含错误信息的异常 39 | // 将标准输出解析为 JSON 对象 40 | const data = JSON.parse(stdout); 41 | return data; // 返回解析后的数据对象 42 | } catch (error) { 43 | throw new Error(`Error: ${error.message}`); // 捕获并抛出错误 44 | } 45 | } -------------------------------------------------------------------------------- /packages/exchange-module/README.md: -------------------------------------------------------------------------------- 1 | # 交易所脚本 2 | 3 | ## api注意事项 4 | 5 | - api权限,一般需要允许现货和杠杆交易,允许提现(根据自己需求增删权限) 6 | - 添加ip地址白名单,只允许白名单里的ip访问api 7 | - okx/bybit提现需要把提现地址添加进免验证提币地址白名单里才可以(不能通过api添加) 8 | - okx举例:EVM地址可以使用okx提供的模版批量上传,一次最多50 9 | - evm地址可以设置成:EVM地址、EVM币种、永久有效期 10 | - sol地址可以设置成:通用地址、solana网络、永久有效期 11 | 12 | ## 文件示例 13 | 14 | 交易所api文件(敏感数据加密,加密方法详见crypt-module模块) 15 | ``` 16 | { 17 | "币安/bybit api示例账户":{ 18 | "main":{ 19 | "apiKey": "加密后的key", 20 | "apiSecret": "加密后的secret", 21 | "apiProxy": ["socks5代理,不加密。创建api时设置只允许受信任的api访问,增加安全性","socks5://xxx:xxx@xxx:xxx"] 22 | }, 23 | "sub1":{ 24 | "apiKey": "加密后的key", 25 | "apiSecret": "加密后的secret", 26 | "apiProxy": ["socks5代理,不加密。创建api时设置只允许受信任的api访问,增加安全性","socks5://xxx:xxx@xxx:xxx"] 27 | } 28 | }, 29 | 30 | 31 | 32 | "欧意api示例账户":{ 33 | "main":{ 34 | "apiKey": "加密后的key", 35 | "apiSecret": "加密后的secret", 36 | "apiPassword": "加密后的密码", 37 | "apiProxy": ["socks5代理,不加密。创建api时设置只允许受信任的api访问,增加安全性","socks5://xxx:xxx@xxx:xxx"] 38 | }, 39 | "sub1":{ 40 | "subAccountName":"xxxxxx", 41 | "apiKey": "加密后的key", 42 | "apiSecret": "加密后的secret", 43 | "apiPassword": "加密后的密码", 44 | "apiProxy": ["socks5代理,不加密。创建api时设置只允许受信任的api访问,增加安全性","socks5://xxx:xxx@xxx:xxx"] 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | 51 | ## 使用示例 52 | 53 | - 提现多少金额到账多少金额,手续费从发送方扣除 54 | - binance只支持通过 充值地址 提现到内部账户(api不能传递uid邮箱等,需要对方的充值地址,输入充值地址api会自动判断为内部地址) 55 | - ok支持只通过 邮箱 提现到内部地址(api支持邮箱、手机号。手机号还需要多一个手机区号参数,我没写这种方式) 56 | - bybit只支持通过 uid 提现到内部地址(api只支持uid) 57 | - 不支持带标签(tag?memo?comment?)的地址提现 58 | - 没有提现的链可自行添加。只测试了用的最多的EVM系和solana系,其他链可自行测试。 59 | - 先小额测试,没问题再大额提现。 60 | 61 | ``` 62 | import { withdraw as binanceWithdraw, priceAlertLoop, priceRangeAlertLoop } from "./binance.js"; 63 | import { withdraw as okxWithdraw } from "./okx.js"; 64 | import { withdraw as bybitWithdraw } from "./bybit.js"; 65 | 66 | // binance转账 67 | // 参数:{ account, chain, toAddress, coin, amount, apiFile='./data/exchange/binance.json' } 68 | await binanceWithdraw({ 69 | account: '你的binance交易所账户,跟api文件里要对应', 70 | chain: 'optimism', 71 | toAddress: '接收地址', 72 | coin: 'usdt', 73 | amount: 5, 74 | apiFile: './data/exchange/binance.json' 75 | }) 76 | 77 | // okx转账 78 | // 参数:{ account, chain, toAddress, coin, amount, withdrawType = 'out', apiFile = './data/exchange/okx.json' } 79 | await okxWithdraw({ 80 | account: '你的okx交易所账户,跟api文件里要对应', 81 | chain: 'optimism', 82 | toAddress: '接收地址', 83 | coin: 'usdt', 84 | amount: 5, 85 | withdrawType: 'out', 86 | apiFile: './data/exchange/okx.json' 87 | }) 88 | 89 | // bybit转账 90 | // 参数:{ account, chain, toAddress, coin, amount, withdrawType = 'out', apiFile = './data/exchange/bybit.json' } 91 | await bybitWithdraw({ 92 | account: '你的okx交易所账户,跟api文件里要对应', 93 | chain: 'optimism', 94 | toAddress:'接收地址', 95 | coin: 'usdt', 96 | amount: 5, 97 | withdrawType: 'out', 98 | apiFile: './data/exchange/bybit.json' 99 | }) 100 | 101 | 102 | // binance交易所价格跌破或涨到警报 参数 { symbol = 'BTC/USDT', price = 100000, waitTime = 600, direction = 'down' } 103 | priceAlertLoop({symbol: 'BTC/USDT', price: 110000, waitTime: 600, direction: 'down'}); 104 | 105 | // binance交易所价格超出区间警报 参数 { symbol = 'BTC/USDT', minPrice = 90000, maxPrice = 110000, waitTime = 600 } 106 | priceRangeAlertLoop({symbol: 'SOL/USDT', minPrice: 253.27, maxPrice: 260.45, waitTime: 300}); 107 | ``` 108 | 109 | ### 官方文档 110 | - ccxt:https://docs.ccxt.com/#/ 111 | - ccxt中文文档:https://www.wuzao.com/document/ccxt/ 112 | - 币安api:https://developers.binance.com/docs/zh-CN/wallet/capital/withdraw 113 | - 欧意api:https://www.okx.com/docs-v5/zh/#overview 114 | - Bybit api:https://bybit-exchange.github.io/docs/zh-TW/api-explorer/v5/category 115 | 116 | 获取币种信息: 117 | - 币安:https://developers.binance.com/docs/zh-CN/wallet/capital 118 | - 欧意:https://www.okx.com/docs-v5/zh/#funding-account-rest-api-get-currencies 119 | - Bybit:https://bybit-exchange.github.io/docs/zh-TW/v5/asset/coin-info 120 | 121 | 提现: 122 | - 币安:https://developers.binance.com/docs/zh-CN/wallet/capital/withdraw 123 | - 欧意:https://www.okx.com/docs-v5/zh/#funding-account-rest-api-withdrawal 124 | - Bybit:https://bybit-exchange.github.io/docs/zh-TW/v5/asset/withdraw 125 | 126 | -------------------------------------------------------------------------------- /packages/exchange-module/bybit.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import ccxt from 'ccxt'; 3 | import { deCryptText } from '../crypt-module/crypt.js'; 4 | 5 | /** 6 | * 根据输入的链名称返回标准化的链名称。 7 | * 支持的链名称包括 ETH/ERC20, TRC/TRC20, POLYGON/MATIC, BSC, AVAX 等, 8 | * 如果输入的链名称不被支持,则抛出错误。 9 | * 10 | * @param {string} chain - 输入的链名称。 11 | * @returns {string} 标准化后的链名称。 12 | * @throws {Error} 当输入的链名称不被支持时抛出错误。 13 | * 14 | * @example 15 | * normalizeChain('ETH') // 返回 'ETH' 16 | * normalizeChain('BSC') // 返回 'BSC' 17 | * normalizeChain('MATIC') // 返回 'MATIC' 18 | */ 19 | function normalizeChain(chain) { 20 | const upperChain = chain.toUpperCase(); 21 | 22 | if (['ETH', 'ERC20', 'ETH MAINNET', 'ETHEREUM'].includes(upperChain)) { 23 | return 'ETH'; 24 | } else if (['TRC', 'TRC20'].includes(upperChain)) { 25 | return 'TRX'; 26 | } else if (['POLYGON', 'MATIC'].includes(upperChain)) { 27 | return 'MATIC'; 28 | } else if (['AVAL', 'AVALANCHE', 'AVAX'].includes(upperChain)) { 29 | return 'CAVAX'; 30 | } else if (['ARB', 'ARBITRUM', 'ARBITRUM ONE'].includes(upperChain)) { 31 | return 'ARBI'; 32 | } else if (['OP', 'OPTIMISM'].includes(upperChain)) { 33 | return 'OP'; 34 | } else if (['BSC', 'BNB', 'BNB Smart Chain'].includes(upperChain)) { 35 | return 'BSC'; 36 | } else if (['ZKS', 'ZKSYNC', 'ERA'].includes(upperChain)) { 37 | return 'ZKV2'; 38 | } else if (['STK', 'STARKNET', 'STRK'].includes(upperChain)) { 39 | return 'STARKNET'; 40 | } else if (['SOL', 'SOLANA'].includes(upperChain)) { 41 | return 'SOL'; 42 | } else if (['LINEA'].includes(upperChain)) { 43 | return 'LINEA'; 44 | } else if (['BASE'].includes(upperChain)) { 45 | return 'BASE'; 46 | } else if (['SUI'].includes(upperChain)) { 47 | return 'SUI'; 48 | } else if (['APTOS', 'APTOS MAINNET', 'APT'].includes(upperChain)) { 49 | return 'APTOS'; 50 | } else { 51 | throw new Error(`${chain} 链不支持,请重新选择`); 52 | } 53 | } 54 | 55 | /** 56 | * 创建并配置一个 Bybit 交易所实例。 57 | * 58 | * @param {Object} params - 创建交易所的参数对象(必填)。 59 | * @param {string} params.account - 要使用的账户名称,用于选择正确的API密钥(必填)。 60 | * @param {string} [params.apiFile='./data/exchange/bybit.json'] - 存储账户信息的 JSON 文件路径。 61 | * @returns {Promise} - 返回一个 Promise,解析为配置好的 Bybit 交易所实例。 62 | * @throws {Error} 在以下情况会抛出错误: 63 | * - 读取API文件失败 64 | * - API密钥解密失败 65 | * - 创建实例失败 66 | * - 代理连接失败 67 | */ 68 | async function createExchange({ account, apiFile = './data/exchange/bybit.json' }) { 69 | // 读取并解析 JSON 文件 70 | const accountApis = JSON.parse(fs.readFileSync(apiFile, 'utf-8')); 71 | const proxys = accountApis[account]['main']['apiProxy']; 72 | const randomproxy = proxys[Math.floor(Math.random() * proxys.length)]; 73 | 74 | // 创建并配置 Bybit 交易所实例 75 | const bybit = new ccxt.bybit({ 76 | 'apiKey': await deCryptText(accountApis[account]['main']['apiKey']), 77 | 'secret': await deCryptText(accountApis[account]['main']['apiSecret']), 78 | 'enableRateLimit': true, 79 | 'options': { 'adjustForTimeDifference': true }, 80 | 'socksProxy': randomproxy, 81 | }); 82 | 83 | return bybit; 84 | } 85 | 86 | /** 87 | * 提现函数,从指定账户提取加密货币到指定地址。 88 | * 89 | * @param {Object} params - 提取参数对象(必填)。 90 | * @param {string} params.account - 要使用的账户名称。 91 | * @param {string} params.chain - 提取的区块链类型,如 'ETH'、'BSC' 等。 92 | * @param {string} params.toAddress - 提取的目标地址。如果 withdrawType 为 'in',必须是6-10位纯数字UID。 93 | * @param {string} params.coin - 要提取的加密货币类型,如 'BTC'、'ETH' 等。 94 | * @param {number} params.amount - 要提取的数量。 95 | * @param {'in'|'out'} [params.withdrawType='out'] - 提现类型。'in' 表示内部转账,'out' 表示链上提现。 96 | * @param {string} [params.apiFile='./data/exchange/bybit.json'] - 存储账户信息的 JSON 文件路径。 97 | * 98 | * @returns {Promise} - 返回一个 Promise,解析为提现操作的结果。 99 | * 100 | * @throws {Error} 在以下情况会抛出错误: 101 | * - 链名称不支持 102 | * - 提现类型不正确 103 | * - 内部转账UID格式错误(必须是6-10位纯数字) 104 | * - 余额不足 105 | * - 提现金额小于最小提现额度 106 | * - API 调用失败 107 | */ 108 | export async function withdraw({ account, chain, toAddress, coin, amount, withdrawType = 'out', apiFile = './data/exchange/bybit.json' }) { 109 | try { 110 | const bybit = await createExchange({ account, apiFile }); 111 | coin = coin.toUpperCase(); 112 | amount = parseFloat(amount); 113 | chain = normalizeChain(chain); 114 | 115 | let forceChain, handlingFee, coinBalance, outMinWithdrawAmount, inMinWithdrawAmount; 116 | // 无需请求api的基本判断放在前面 117 | if (!['in', 'out'].includes(withdrawType)) { console.log('withdrawType 只能是 "in" 或 "out"'); return; } 118 | const isValidBybitUID = (uid) => /^\d{6,10}$/.test(uid); 119 | if (withdrawType === 'in' && !isValidBybitUID(toAddress)) { console.log('内部地址仅支持UID(6-10位纯数字)'); return; } 120 | if (withdrawType === 'out' && isValidBybitUID(toAddress)) { console.log('外部地址输入有误,请检查地址是否正确'); return; } 121 | 122 | try { // 获取余额 123 | // 使用bybit的api:GET /v5/asset/transfer/query-account-coins-balance也可以获取地址余额,transferBalance字段 124 | // const bal = await bybit.privateGetV5AssetTransferQueryAccountCoinsBalance({ accountType: 'FUND', coin: 'USDC' }); 125 | const allBalance = await bybit.fetchBalance({ "type": "funding" }); // funding 表示资金账户 126 | // console.log(allBalance) 127 | coinBalance = allBalance[coin]?.free || 0.0; 128 | console.log(`${account} 资金账户现有 ${coinBalance} ${coin}`); 129 | } catch (error) { 130 | console.log('获取余额失败,请检查账户是否正确.', error) 131 | return; 132 | } 133 | 134 | try { // 获取币种信息 135 | const currencies = await bybit.privateGetV5AssetCoinQueryInfo({ coin }); 136 | const coinInfo = currencies.result.rows[0].chains; 137 | const coinInfoOfChain = coinInfo.find(row => row.chain === chain); 138 | const withdrawFee = parseFloat(coinInfoOfChain.withdrawFee); 139 | outMinWithdrawAmount = parseFloat(coinInfoOfChain.withdrawMin); 140 | inMinWithdrawAmount = parseFloat(coinInfoOfChain.chainWithdraw); // 问了客服,内部转账api没有最小提币额的对应字段,我根据页面检查了一下,chainWithdraw这个字段挺符合的,暂时用这个 141 | const withdrawPercentageFee = parseFloat(coinInfoOfChain.withdrawPercentageFee); 142 | if (withdrawPercentageFee != 0) { 143 | handlingFee = amount / (1 - withdrawPercentageFee) * withdrawPercentageFee + withdrawFee; 144 | } else { 145 | handlingFee = withdrawFee; 146 | } 147 | } catch (error) { 148 | console.log('获取手续费失败,请检查账户是否正确.', error) 149 | return; 150 | } 151 | 152 | if (withdrawType === 'out') { 153 | if (amount < outMinWithdrawAmount) { 154 | console.log(`${chain} 链转账 ${coin} 到外部地址 ${toAddress} 最小提现数量为 ${outMinWithdrawAmount} ${coin}`); 155 | return; 156 | } 157 | forceChain = 0; 158 | console.log(`${chain} 链转账 ${coin} 到外部地址 ${toAddress} 手续费为 ${handlingFee} ${coin}`); 159 | } else if (withdrawType === 'in') { 160 | if (amount < inMinWithdrawAmount) { 161 | console.log(`${chain} 链转账 ${coin} 到内部地址 ${toAddress} 最小提现数量为 ${inMinWithdrawAmount} ${coin}`); 162 | return; 163 | } 164 | forceChain = 2; 165 | handlingFee = 0; 166 | console.log(`${chain} 链转账 ${coin} 到内部地址 ${toAddress} 手续费为 ${handlingFee} ${coin}`); 167 | }; 168 | 169 | if (amount + handlingFee > coinBalance) { 170 | console.log('提现金额超出余额,请先充值或者减少提现数量'); 171 | return; 172 | } 173 | 174 | const response = await bybit.withdraw(coin, amount, toAddress, undefined, { 175 | chain, 176 | accountType: 'FUND', // 统一账户2.0 出金用资金账户 177 | feeType: 0, // 0: (默认)提现多少到账多少,需要考虑手续费中,账户余额要 >= 提现金额+手续费, 1: 到账金额为提现金额减手续费(手续费系统自动计算) 178 | forceChain, // 0(默认):如果地址解析出是内部地址,则内部转账(仅限Bybit主账户)1:强制提现发生在链上 2: 使用UID提现 179 | }); 180 | 181 | console.log(`账户 ${account} 通过 ${chain} 链 提现 ${amount} ${coin} 到地址 ${toAddress} 请求已提交,等待确认。手续费为 ${handlingFee} ${coin}`); 182 | return response; 183 | 184 | } catch (error) { 185 | console.error(`提现错误: ${error.message}`); 186 | throw error; 187 | } 188 | } -------------------------------------------------------------------------------- /packages/exchange-module/okx.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import dns from 'dns'; 3 | import ccxt from 'ccxt'; 4 | import { deCryptText } from '../crypt-module/crypt.js'; 5 | 6 | // 奇怪,这里要强制 Node.js 使用 IPv4,才能跑通请求。为啥默认是 IPv6?之前使用也没出现这个问题。先这么解决,后续再研究。 7 | dns.setDefaultResultOrder('ipv4first'); // 优先使用 IPv4 8 | 9 | /** 10 | * 根据输入的链名称返回标准化的链名称。 11 | * 支持的链名称包括 ETH/ERC20, TRC/TRC20, POLYGON/MATIC 等, 12 | * 如果输入的链名称不被支持,则抛出错误。 13 | * 14 | * @param {string} chain - 输入的链名称。 15 | * @returns {string} 标准化后的链名称。 16 | */ 17 | function normalizeChain(chain) { 18 | const upperChain = chain.toUpperCase(); // 将输入转换为大写以忽略大小写差异 19 | 20 | // 检查输入的链名称是否属于已知的链名称,并返回相应的标准化名称 21 | if (['ETH', 'ERC20'].includes(upperChain)) { 22 | return 'ERC20'; 23 | } else if (['TRC', 'TRC20'].includes(upperChain)) { 24 | return 'TRC20'; 25 | } else if (['POLYGON', 'MATIC'].includes(upperChain)) { 26 | return 'Polygon'; 27 | } else if (['BSC', 'BNB'].includes(upperChain)) { 28 | return 'BSC'; 29 | } else if (['APT', 'APTOS'].includes(upperChain)) { 30 | return 'Aptos'; 31 | } else if (['AVAL', 'AVALANCHE', 'AVAX'].includes(upperChain)) { 32 | return 'Avalanche C-Chain'; 33 | } else if (['ARB', 'ARBITRUM', 'ARBITRUM ONE'].includes(upperChain)) { 34 | return 'Arbitrum One'; 35 | } else if (['OP', 'OPTIMISM'].includes(upperChain)) { 36 | return 'Optimism'; 37 | } else if (['ZKS', 'ZKSYNC', 'ERA'].includes(upperChain)) { 38 | return 'zkSync Era'; 39 | } else if (['STK', 'STARKNET', 'STRK'].includes(upperChain)) { 40 | return 'Starknet'; 41 | } else if (['SOL', 'SOLANA'].includes(upperChain)) { 42 | return 'Solana'; 43 | } else if (['LINEA'].includes(upperChain)) { 44 | return 'Linea'; 45 | } else if (['BASE'].includes(upperChain)) { 46 | return 'Base'; 47 | } else if (['SUI'].includes(upperChain)) { 48 | return 'Sui'; 49 | } else { 50 | // 如果输入的链名称不被支持,抛出错误 51 | throw new Error(`${chain} 链不支持,请重新选择`); 52 | } 53 | } 54 | 55 | /** 56 | * 创建并配置一个交易所实例。 57 | * @param {Object} params - 创建交易所的参数对象(必填)。 58 | * @param {string} params.account - 要使用的账户名称,用于选择正确的API密钥(必填)。 59 | * @param {string} [params.apiFile='./data/exchange/okx.json'] - 存储账户信息的 JSON 文件路径。 60 | * @returns {Promise} - 返回一个 Promise,解析为配置好的 OKX 交易所实例。 61 | * @throws {Error} 当读取API文件失败、API密钥解密失败或创建实例失败时抛出错误。 62 | */ 63 | async function createExchange({ account, apiFile = './data/exchange/okx.json' }) { 64 | 65 | // 异步读取文件并解析JSON 66 | const accountApis = JSON.parse(fs.readFileSync(apiFile, 'utf-8')); 67 | const proxys = accountApis[account]['main']['apiProxy'] 68 | const randomproxy = proxys[Math.floor(Math.random() * proxys.length)]; 69 | // 创建并配置okx交易所实例 70 | const okx = new ccxt.okx({ 71 | 'apiKey': await deCryptText(accountApis[account]['main']['apiKey']), 72 | 'secret': await deCryptText(accountApis[account]['main']['apiSecret']), 73 | 'password': await deCryptText(accountApis[account]['main']['apiPassword']), 74 | 'enableRateLimit': true, // 启用请求速率限制 75 | 'options': { 'adjustForTimeDifference': true }, // 自动调整时间戳以适应本地计算机的时区差异 76 | 'socksProxy': randomproxy, // 使用提供的代理 77 | }); 78 | 79 | return okx; 80 | } 81 | 82 | /** 83 | * 提现函数,从指定账户提取加密货币到指定地址。 84 | * 85 | * @param {Object} params - 提取参数对象(必填)。 86 | * @param {string} params.account - 要使用的账户名称。 87 | * @param {string} params.chain - 提取的区块链类型,如 'ETH'、'BSC' 等。 88 | * @param {string} params.toAddress - 提取的目标地址。如果 withdrawType 为 'in',必须是邮箱地址。 89 | * @param {string} params.coin - 要提取的加密货币类型,如 'BTC'、'ETH' 等。 90 | * @param {number} params.amount - 要提取的数量。 91 | * @param {'in'|'out'} [params.withdrawType='out'] - 提现类型。'in' 表示内部转账,'out' 表示链上提现。 92 | * @param {string} [params.apiFile='./data/exchange/okx.json'] - 存储账户信息的 JSON 文件路径。 93 | * 94 | * @returns {Promise} - 提现操作不返回值,但会在控制台输出操作结果。 95 | * 96 | * @throws {Error} 在以下情况会抛出错误: 97 | * - 链名称不支持 98 | * - 提现类型不正确 99 | * - 内部转账地址格式错误 100 | * - 余额不足 101 | * - 提现金额小于最小提现额度 102 | * - API 调用失败 103 | */ 104 | export async function withdraw({ account, chain, toAddress, coin, amount, withdrawType = 'out', apiFile = './data/exchange/okx.json' }) { 105 | 106 | try { 107 | coin = coin.toUpperCase() 108 | amount = parseFloat(amount); 109 | chain = normalizeChain(chain) 110 | let dest, handlingFee, coinBalance, outMinWithdrawAmount, inMinWithdrawAmount; // dest=3代表内部地址,4代表外部地址 111 | // 无需请求api的基本判断放在前面 112 | if (!['in', 'out'].includes(withdrawType)) { console.log('withdrawType 只能是 "in" 或 "out"'); return; } 113 | if (withdrawType === 'in' && !toAddress.includes('@')) { console.log('内部地址仅支持邮箱地址'); return; } 114 | if (withdrawType === 'out' && toAddress.includes('@')) { console.log('外部地址输入有误,请检查地址是否正确'); return; } 115 | 116 | const okx = await createExchange({ account, apiFile }) 117 | 118 | try { // 获取余额 119 | const allBalance = await okx.fetchBalance({ "type": "funding" }) // funding 表示资金账户 120 | // console.log(allBalance) 121 | coinBalance = allBalance[coin]?.free || 0.0; 122 | // console.log(typeof(coinBalance)) 123 | console.log(`${account} 资金账户现有 ${coinBalance} ${coin}`); 124 | } catch (error) { 125 | console.log('获取余额失败,请检查账户是否正确.', error) 126 | return; 127 | } 128 | 129 | try { // 获取币种信息 130 | const currencyInfo = await okx.privateGetAssetCurrencies({ ccy: coin }); 131 | // console.log(currencyInfo['data']) 132 | const currencyData = currencyInfo['data'].find(data => data.chain === `${coin}-${chain}`); 133 | // console.log(currencyData) 134 | handlingFee = parseFloat(currencyData.minFee); 135 | outMinWithdrawAmount = parseFloat(currencyData.minWd); 136 | inMinWithdrawAmount = parseFloat(currencyData.minInternal); 137 | // 提币精度(如果需要)withdrawPrecision = parseInt(currencyData.wdTickSz); 138 | } catch (error) { 139 | console.log('获取币种信息失败,请检查链名称或币名称是否正确.', error) 140 | return; 141 | } 142 | 143 | if (withdrawType === 'out') { 144 | if (amount < outMinWithdrawAmount) { 145 | console.log(`${chain} 链转账 ${coin} 到外部地址 ${toAddress} 最小提现数量为 ${outMinWithdrawAmount} ${coin}`); 146 | return; 147 | } 148 | dest = 4; 149 | console.log(`${chain} 链转账 ${coin} 到外部地址 ${toAddress} 手续费为 ${handlingFee} ${coin}`); 150 | } else if (withdrawType === 'in') { 151 | if (amount < inMinWithdrawAmount) { 152 | console.log(`${chain} 链转账 ${coin} 到内部地址 ${toAddress} 最小提现数量为 ${inMinWithdrawAmount} ${coin}`); 153 | return; 154 | } 155 | dest = 3; 156 | handlingFee = 0; 157 | console.log(`${chain} 链转账 ${coin} 到内部地址 ${toAddress} 手续费为 ${handlingFee} ${coin}`); 158 | }; 159 | 160 | if (amount + handlingFee > coinBalance) { 161 | console.log('提现金额超出余额,请先充值或者减少提现数量'); 162 | return; 163 | } 164 | 165 | // 执行提现操作 166 | await okx.withdraw(coin, amount, toAddress, undefined, { 167 | dest, // 3代表内部地址,4代表外部地址 168 | network: chain, 169 | fee: handlingFee, 170 | password: '', //提现funding密码,没有,留空 171 | }); 172 | console.log(`账户 ${account} 通过 ${chain} 链 提现 ${amount} ${coin} 到地址 ${toAddress} 请求已提交,等待确认。手续费为 ${handlingFee} ${coin}`); 173 | } catch (error) { 174 | console.error(`提现错误: ${error}`); 175 | } 176 | } -------------------------------------------------------------------------------- /packages/notification-module/README.md: -------------------------------------------------------------------------------- 1 | # 通知/日志模块 2 | 3 | 1. 通知管理器 - 用于控制台输出和文件日志记录 4 | 2. 钉钉机器人 - 用于发送消息到钉钉群 5 | 6 | ## 通知管理器 (NotificationManager) 7 | 8 | 通知管理器用于统一管理系统中的消息提示,支持控制台输出和文件日志记录。 9 | 10 | ### 基本用法 11 | 12 | ```javascript 13 | import { notificationManager } from './notification.js'; 14 | 15 | // 1. 简单消息(无上下文) 16 | notificationManager.info('这是一条普通信息'); 17 | notificationManager.success('这是一条成功信息'); // 绿色显示 18 | notificationManager.warning('这是一条警告信息'); // 黄色显示 19 | notificationManager.error('这是一条错误信息'); // 红色显示 20 | 21 | // 2. 带上下文的对象形式(推荐) 22 | notificationManager.info({ 23 | message: '代理服务器启动', 24 | context: { module: 'proxy', id: 1 } 25 | }); 26 | // 输出: 代理服务器启动 [module proxy] [id 1] 27 | 28 | // 3. 仅显示上下文(message为空) 29 | notificationManager.info({ 30 | context: { module: 'proxy', status: 'running' } 31 | }); 32 | // 输出: [module proxy] [status running] 33 | 34 | // 4. 带上下文的链式调用 35 | notificationManager 36 | .withContext({ module: 'proxy', id: 1 }) 37 | .success('代理服务器启动'); 38 | // 输出: 代理服务器启动 [module proxy] [id 1] 39 | 40 | // 5. 完整对象形式 41 | notificationManager.notify({ 42 | message: '代理服务器启动', // message 可以为空 43 | type: 'SUCCESS', // 可选,默认 'INFO' 44 | context: { module: 'proxy', id: 1 } // 可选 45 | }); 46 | // 输出: 代理服务器启动 [module proxy] [id 1] 47 | 48 | // 4. 持久化上下文 49 | notificationManager.withContext({ module: 'proxy' }); 50 | notificationManager.info('开始初始化'); // [module proxy] 开始初始化 51 | notificationManager.success('初始化完成'); // [module proxy] 初始化完成 52 | notificationManager.clearContext(); // 清除上下文 53 | ``` 54 | 55 | ### 配置选项 56 | 57 | 通过 `configure` 方法可以自定义全局配置: 58 | 59 | ```javascript 60 | notificationManager.configure({ 61 | showTimestamp: true, // 是否显示时间戳,默认 true 62 | logToConsole: true, // 是否在控制台打印,默认 true 63 | logToFile: true, // 是否写入日志文件,默认 true 64 | logDir: 'logs', // 日志目录,默认 'logs' 65 | logRetentionDays: 7, // 日志保留天数,默认 7 天 66 | contextFirst: false // 控制上下文和消息的顺序,false 表示消息在前,true 表示上下文在前 67 | }); 68 | ``` 69 | 70 | 所有配置项都是可选的,只需要传入你想修改的选项。 71 | 72 | ### 消息级别配置 73 | 74 | 除了全局配置外,还支持针对单条消息的配置,可以覆盖全局配置: 75 | 76 | ```javascript 77 | // 示例1:只写入日志,不在控制台显示 78 | notificationManager.info({ 79 | message: "后台任务执行成功", 80 | context: { taskId: "123" }, 81 | config: { 82 | logToConsole: false, // 覆盖全局配置,不在控制台显示 83 | logToFile: true // 写入日志文件 84 | } 85 | }); 86 | 87 | // 示例2:只在控制台显示,不写入日志 88 | notificationManager.success({ 89 | message: "临时调试信息", 90 | config: { 91 | logToConsole: true, // 在控制台显示 92 | logToFile: false // 不写入日志文件 93 | } 94 | }); 95 | 96 | // 示例3:完全自定义配置 97 | notificationManager.info({ 98 | message: "自定义显示的消息", 99 | context: { module: "test" }, 100 | config: { 101 | showTimestamp: false, // 不显示时间戳 102 | logToConsole: true, // 在控制台显示 103 | logToFile: true, // 写入日志文件 104 | contextFirst: true // 上下文显示在消息前面 105 | } 106 | }); 107 | ``` 108 | 109 | 消息级别配置的优先级高于全局配置,但仅对当前消息有效。未指定的配置项会使用全局配置。 110 | 111 | #### 常见配置场景 112 | 113 | 1. 修改消息顺序: 114 | ```javascript 115 | // 消息在前(默认) 116 | notificationManager.configure({ contextFirst: false }); 117 | // 输出: 代理服务器启动 [module proxy] [id 1] 118 | 119 | // 上下文在前 120 | notificationManager.configure({ contextFirst: true }); 121 | // 输出: [module proxy] [id 1] 代理服务器启动 122 | ``` 123 | 124 | 2. 修改日志目录: 125 | ```javascript 126 | notificationManager.configure({ 127 | logDir: 'logs/proxy' // 日志将保存在 logs/proxy 目录 128 | }); 129 | ``` 130 | 131 | 3. 禁用控制台输出: 132 | ```javascript 133 | notificationManager.configure({ 134 | logToConsole: false // 只写入文件,不在控制台显示 135 | }); 136 | ``` 137 | 138 | 4. 禁用文件日志: 139 | ```javascript 140 | notificationManager.configure({ 141 | logToFile: false // 只在控制台显示,不写入文件 142 | }); 143 | ``` 144 | 145 | 4. 禁用时间戳: 146 | ```javascript 147 | notificationManager.configure({ 148 | showTimestamp: false // 消息前不显示时间戳 149 | }); 150 | ``` 151 | 152 | ### 上下文管理 153 | 154 | 上下文用于为消息添加额外的标识信息,支持三种级别的上下文: 155 | 156 | 1. 全局上下文(通过setGlobalContext设置) 157 | ```javascript 158 | // 设置全局上下文,适用于整个应用生命周期 159 | notificationManager.setGlobalContext({ 160 | module: 'proxy', 161 | version: '1.0' 162 | }); 163 | ``` 164 | 165 | 2. 持久上下文(通过withContext设置) 166 | ```javascript 167 | // 设置持久上下文 168 | notificationManager.withContext({ module: 'proxy' }); 169 | 170 | // 后续所有消息都会带上这个上下文,直到被清除 171 | notificationManager.info('开始初始化'); // [module proxy] 开始初始化 172 | notificationManager.success('初始化完成'); // [module proxy] 初始化完成 173 | 174 | // 需要手动清除持久上下文 175 | notificationManager.clearContext(); 176 | ``` 177 | 178 | 3. 临时上下文(仅对当前消息有效) 179 | ```javascript 180 | // 方式1:链式调用 181 | notificationManager 182 | .withContext({ id: 1 }) 183 | .info('服务启动'); // 消息发送后自动清除上下文 184 | 185 | // 方式2:消息对象中指定 186 | notificationManager.info({ 187 | message: '服务启动', 188 | context: { id: 1 } 189 | }); 190 | 191 | // 两种方式效果完全相同,都是一次性的 192 | ``` 193 | 194 | 上下文优先级:临时上下文 > 持久上下文 > 全局上下文 195 | 196 | 示例: 197 | ```javascript 198 | // 1. 设置全局上下文 199 | notificationManager.setGlobalContext({ 200 | module: 'proxy', 201 | env: 'prod' 202 | }); 203 | 204 | // 2. 设置持久上下文 205 | notificationManager.withContext({ status: 'running' }); 206 | 207 | // 3. 使用临时上下文(会覆盖同名的持久上下文和全局上下文) 208 | notificationManager.info({ 209 | message: '配置更新', 210 | context: { 211 | env: 'test', // 覆盖全局上下文中的 env 212 | status: 'idle' // 覆盖持久上下文中的 status 213 | } 214 | }); 215 | // 输出: 配置更新 [module proxy] [env test] [status idle] 216 | 217 | // 持久上下文仍然存在 218 | notificationManager.info('继续检查'); // [module proxy] [env prod] [status running] 219 | 220 | // 清除持久上下文 221 | notificationManager.clearContext(); 222 | ``` 223 | 224 | ### 日志文件 225 | 226 | - 日志文件按天生成,文件名格式:`YYYY-MM-DD.log` 227 | - 日志内容格式:`[时间戳] [类型] [上下文] 消息内容` 228 | - 默认保留最近7天的日志文件 229 | - 使用北京时间(UTC+8) 230 | 231 | #### 日志示例 232 | 233 | ``` 234 | [2024-03-21_14:30:45] [INFO] 代理服务器启动 [module proxy] [id 1] 235 | [2024-03-21_14:30:46] [SUCCESS] 初始化完成 236 | [2024-03-21_14:30:47] [WARNING] 连接超时 237 | [2024-03-21_14:30:48] [ERROR] 服务异常 238 | ``` 239 | 240 | ### 日志清理 241 | 242 | 系统会自动清理过期的日志文件(默认7天),也可以手动清理: 243 | 244 | ```javascript 245 | notificationManager.cleanLogs(); // 清理过期日志文件 246 | ``` 247 | 248 | ### 注意事项 249 | 250 | 1. 消息会立即在控制台显示,日志文件异步写入 251 | 2. 日志目录会自动创建,无需手动创建 252 | 3. 如果写入日志失败,会在控制台输出错误信息,但不会影响程序运行 253 | 4. 建议在程序启动时进行一次配置,之后使用默认配置 254 | 5. 控制台彩色输出在某些终端可能不支持 255 | 6. 临时上下文在消息发送后会自动清除 256 | 7. 持久化上下文需要手动调用 clearContext() 清除 257 | 258 | ## 钉钉机器人通知 259 | 260 | ### 1. 创建钉钉群 261 | 262 | 1. 点击左上角 "+" 号 263 | 2. 选择"发起群聊" 264 | 3. 输入群名称(如:Web3 通知群) 265 | 4. 点击"创建"完成群创建 266 | 267 | ### 2. 创建钉钉机器人(需要电脑端) 268 | 269 | 1. 在已创建的群聊中,点击群设置(右上角) 270 | 2. 选择"智能群助手" 271 | 3. 点击"添加机器人" 272 | 4. 选择"自定义"机器人 273 | 5. 机器人名字随意设置(如:Web3 通知助手) 274 | 6. 安全设置选择以下任一方式: 275 | - 方式一:选择"自定义关键词",填写消息中必须包含的关键词(我用的此方式,关键词为:web3消息通知) 276 | - 方式二:选择"加签"(更安全) 277 | 7. 点击"完成",复制生成的 Webhook 地址 278 | 8. 从 Webhook 地址中提取 access_token(https://oapi.dingtalk.com/robot/send?access_token=xxx 中的 xxx 部分) 279 | 280 | ### 3. 配置文件 281 | 282 | .env文件添加 283 | ``` 284 | # 钉钉机器人access_token 285 | dingtalkAccessToken = 'XXXXXX' 286 | ``` 287 | 288 | ### 使用示例 289 | 290 | ```javascript 291 | import { dingdingNotifier } from './notifier.js'; 292 | 293 | // 发送消息 294 | await dingdingNotifier('测试消息', 'web3消息通知'); // 如果你设置的关键词为:web3消息通知,则可以忽略此参数 295 | ``` 296 | 297 | ### 官方文档 298 | - https://open.dingtalk.com/document/robots/custom-robot-access -------------------------------------------------------------------------------- /packages/notification-module/notifier.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import 'dotenv/config'; 3 | import { withRetry } from '../utils-module/retry.js'; 4 | import { getCurrentTime } from '../utils-module/utils.js'; 5 | /** 6 | * 发送钉钉通知消息 7 | * @param {string} content - 要发送的消息内容 8 | * @param {string} [keyWord='web3消息通知'] - 消息关键词,默认为'web3消息通知',这个是在添加机器人时设置的。 9 | * @returns {Promise} 钉钉 API 的响应结果 10 | * @throws {Error} 当发送消息失败时抛出错误 11 | */ 12 | export async function dingdingNotifier(content, keyWord = 'web3消息通知') { 13 | const url = 'https://oapi.dingtalk.com/robot/send?access_token=' + process.env.dingtalkAccessToken; 14 | const msg = { 15 | "msgtype": "text", 16 | "text": { "content": keyWord + '\n' + content + '\n预警时间: ' + getCurrentTime() } 17 | }; 18 | 19 | withRetry(async () => { 20 | const response = await axios.post(url, msg, { 21 | headers: { "Content-Type": "application/json;charset=utf-8" }, 22 | timeout: 10000 // 10秒超时 23 | }); 24 | return response.data; 25 | }, { 26 | taskName: '钉钉通知信息发送', 27 | }); 28 | } -------------------------------------------------------------------------------- /packages/rpa-module/README.md: -------------------------------------------------------------------------------- 1 | # RPA 模块使用指南 2 | 3 | RPA (Robotic Process Automation) 模块提供了一系列自动化工具,用于浏览器自动化操作,支持多种浏览器环境。 4 | 5 | ## 浏览器环境配置系统 6 | 7 | 为了避免代码重复并简化开发,我们提供了统一的浏览器环境配置系统。这允许不同的工具类轻松地支持多种浏览器环境,如Chrome和BitBrowser。 8 | 9 | ### 基本使用 10 | 11 | 在创建工具类时,使用共享的浏览器配置来简化代码。 12 | 13 | ```javascript 14 | import { createBrowserUtil } from '../../rpa-module/browserConfig.js'; 15 | 16 | // 使用统一的创建函数来获取浏览器实例 17 | const browserUtil = await createBrowserUtil({ 18 | browserType: 'chrome', // 或 'bitbrowser' 19 | browserId: 1 // Chrome实例编号或BitBrowser ID 20 | }); 21 | ``` 22 | 23 | ### 创建支持多浏览器环境的工具类 24 | 25 | 以下是创建支持多浏览器环境的工具类模板: 26 | 27 | ```javascript 28 | import { createBrowserUtil } from '../../rpa-module/browserConfig.js'; 29 | 30 | export class MyToolClass { 31 | constructor(browserUtil) { 32 | this.browserUtil = browserUtil; 33 | this.page = browserUtil.page; 34 | this.context = browserUtil.context; 35 | } 36 | 37 | static async create({ browserType = 'chrome', browserId, ...otherParams }) { 38 | try { 39 | // 使用共享的浏览器工具创建函数 40 | const browserUtil = await createBrowserUtil({ browserType, browserId }); 41 | 42 | // 创建工具类实例 43 | const instance = new MyToolClass(browserUtil); 44 | 45 | // 进行其他初始化... 46 | 47 | return instance; 48 | } catch (error) { 49 | console.error('创建实例失败:', error); 50 | throw error; 51 | } 52 | } 53 | 54 | // 实现其他方法... 55 | } 56 | ``` 57 | 58 | ### 添加新的浏览器类型支持 59 | 60 | 如果需要添加新的浏览器类型支持,只需在 `browserConfig.js` 文件中的 `browserConfigs` 对象中添加新的配置: 61 | 62 | ```javascript 63 | // 在 browserConfig.js 中 64 | export const browserConfigs = { 65 | chrome: { 66 | baseClass: ChromeBrowserUtil, 67 | createParams: (browserId) => ({ chromeNumber: browserId }) 68 | }, 69 | bitbrowser: { 70 | baseClass: BitBrowserUtil, 71 | createParams: (browserId) => ({ browserId }) 72 | }, 73 | // 添加新的浏览器类型 74 | newBrowser: { 75 | baseClass: NewBrowserUtil, 76 | createParams: (browserId) => ({ yourParamName: browserId }) 77 | } 78 | }; 79 | ``` 80 | 81 | ## 现有工具类 82 | 83 | ### ChromeBrowserUtil 84 | 85 | 基于Playwright的Chrome浏览器自动化工具。 86 | 87 | ```javascript 88 | const chrome = await ChromeBrowserUtil.create({ chromeNumber: 1 }); 89 | ``` 90 | 91 | ### BitBrowserUtil 92 | 93 | 基于BitBrowser的浏览器自动化工具。 94 | 95 | ```javascript 96 | const bitBrowser = await BitBrowserUtil.create({ browserId: 'your-browser-id' }); 97 | ``` 98 | 99 | ### OkxWalletUtil 100 | 101 | OKX钱包工具类,支持Chrome和BitBrowser环境。 102 | 103 | ```javascript 104 | // Chrome环境 105 | const okxWalletChrome = await OkxWalletUtil.create({ 106 | browserType: 'chrome', 107 | browserId: 1 108 | }); 109 | 110 | // BitBrowser环境 111 | const okxWalletBit = await OkxWalletUtil.create({ 112 | browserType: 'bitbrowser', 113 | browserId: 'your-browser-id' 114 | }); 115 | ``` 116 | 117 | ## 最佳实践 118 | 119 | 1. 使用共享的浏览器配置,而不是在每个工具类中重复定义浏览器配置逻辑 120 | 2. 将与特定工具相关的配置(如钱包ID)单独保存,不要混入通用的浏览器配置 121 | 3. 尽可能使用统一的接口方法命名和参数传递方式 122 | 4. 在工具类的静态create方法中处理错误,提供有意义的错误信息 123 | 124 | 125 | ## 学习资料 126 | - 新兴爬虫利器 Playwright 的基本用法:https://cuiqingcai.com/36045.html 127 | - Playwright自动化测试工具-微软出品-支持三大浏览器:https://www.jianshu.com/p/744d5491fd66 128 | - Python如何爬虫?玩转新一代爬虫神器Playwright!:https://zhuanlan.zhihu.com/p/493300801 129 | - Playwright: 比 Puppeteer 更好用的浏览器自动化工具:https://yifei.me/note/2226 130 | 131 | playwright基本概念 132 | - browser(浏览器):支持多种浏览器:Chromium(chrome、edge)、Firefox、WebKit(Safari),一般每一种浏览器只需要创建一个browser实例。 133 | - context(上下文):一个浏览器实例可以有多个context,将浏览器分割成不同的上下文,以实现会话的分离,如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可。 134 | - page(页面):一个context下可以有多个page,一个page就代表一个浏览器的标签页或弹出窗口,用于进行页面操作。 135 | - frame一个页面至少包含一个主frame,新的frame通过iframe标签定义,frame之间可以进行嵌套,只有先定位到frame才能对frame里面的元素进行定位和操作。playwright默认使用page进行的元素操作会重定向到主frame上。 136 | -------------------------------------------------------------------------------- /packages/rpa-module/bitbrowser/bitbrowser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * bitbrowser api : https://doc.bitbrowser.cn/api-jie-kou-wen-dang/liu-lan-qi-jie-kou 3 | * playwright文档: https://playwright.dev/docs/library 4 | */ 5 | import playwright from 'playwright'; 6 | import axios from 'axios'; 7 | import { sleep } from '../../utils-module/utils.js'; 8 | 9 | const bitbrowserUrl = 'http://127.0.0.1:54345' 10 | 11 | /** 12 | * 创建或修改浏览器窗口 13 | * @param {string} browserOs - 操作系统,影响ua值。最好跟本机操作系统相同 14 | * @param {string} browserId - bitbrowser浏览器id。此参数空值时代表创建浏览器。有值时代表修改浏览器信息 15 | * @returns {Promise} 浏览器id 16 | */ 17 | export const createOrUpdateBrowser = async ({ browserOs = 'mac', browserId = '', coreVersion = '134', browserVersion = '130' }) => { 18 | // 操作系统 19 | browserOs = ['mac', 'macos'].includes(browserOs) ? 'MacIntel' 20 | : ['win', 'windows'].includes(browserOs) ? 'Win32' 21 | : browserOs; 22 | 23 | const body = { 24 | id: browserId, // 有值时为修改,无值是添加 25 | platform: 'https://www.google.com', // 账号平台 26 | platformIcon: 'other', // 取账号平台的 hostname 或者设置为other 27 | workbench: 'localServer', // 浏览器窗口工作台页面。chuhai2345(默认)|localServer|disable 28 | proxyMethod: 2, // 代理类型 1平台 2自定义 29 | // agentId: '', // proxyMethod为1时,平台代理IP的id 30 | // 自定义代理类型 ['noproxy', 'http', 'https', 'socks5', '911s5'] 31 | proxyType: 'noproxy', // 先不设置代理。可在修改代理接口去设置 32 | browserFingerPrint: { 33 | coreVersion, // 内核版本 34 | ostype: 'PC', // 操作系统平台 PC | Android | IOS 35 | version: browserVersion, // 浏览器版本,建议92以上,不填则会从92以上版本随机。 36 | os: browserOs, // 为navigator.platform值 Win32 | Linux i686 | Linux armv7l | MacIntel 37 | // userAgent: "", // ua,不填则自动生成 38 | isIpCreateTimeZone: false, // 基于IP生成对应的时区 39 | timeZone: 'GMT+08:00', // 时区,isIpCreateTimeZone 为false时,参考附录中的时区列表 40 | position: '1', // 网站请求获取您当前位置时,是否允许 0询问|1允许|2禁止 41 | isIpCreatePosition: true, // 是否基于IP生成对应的地理位置 42 | lat: '', // 经度 isIpCreatePosition 为false时设置 43 | lng: '', // 纬度 isIpCreatePosition 为false时设置 44 | precisionData: '', // 精度米 isIpCreatePosition 为false时设置 45 | isIpCreateLanguage: false, // 是否基于IP生成对应国家的浏览器语言 46 | languages: 'zh-CN', // isIpCreateLanguage 为false时设置,值参考附录 47 | isIpCreateDisplayLanguage: false, // 是否基于IP生成对应国家的浏览器界面语言 48 | displayLanguages: '', // isIpCreateDisplayLanguage 为false时设置,默认为空,即跟随系统 49 | WebRTC: 0, // 0替换(默认)|1允许|2禁止。开启WebRTC,将公网ip替换为代理ip,同时掩盖本地ip 50 | } 51 | }; 52 | 53 | const response = await axios.post(`${bitbrowserUrl}/browser/update`, body); 54 | // console.log(response.data) 55 | console.log('创建或修改浏览器成功,浏览器id为:', response.data.data.id); 56 | return response.data.data.id; 57 | }; 58 | 59 | 60 | export async function updateBitbrowserProxy({ id, host, post, username, password }) { 61 | try { 62 | const response = await axios.post(`${bitbrowserUrl}/browser/proxy/update`, { 63 | ids: [id], 64 | ipCheckService: 'ip-api', 65 | proxyMethod: 2, //自定义代理 66 | proxyType: 'socks5', 67 | host: host, 68 | port: post, 69 | proxyUserName: username, 70 | proxyPassword: password 71 | }); 72 | 73 | // console.log(response) 74 | if (response.data.success) { 75 | console.log('修改代理成功') 76 | } else { 77 | console.log('修改代理失败') 78 | } 79 | } catch (error) { console.log(error) } 80 | } 81 | 82 | export class BitBrowserUtil { 83 | /** 84 | * BitBrowser浏览器管理工具 85 | * @param {string} browserId - BitBrowser浏览器ID 86 | * @param {number} [navigationWaitTime=30] - 页面导航等待时间(秒) 87 | * @param {number} [allWaitTime=30] - 全局等待时间(秒) 88 | * @param {number} [maxRetries=3] - 最大重试次数 89 | */ 90 | constructor(browserId) { 91 | this.browserId = browserId; 92 | this.browser = null; 93 | this.context = null; 94 | this.page = null; 95 | this.isStarted = false; 96 | } 97 | 98 | /** 99 | * 创建并初始化BitBrowser实例 100 | * @static 101 | * @param {Object} params - 初始化参数 102 | * @param {string} params.browserId - BitBrowser浏览器ID 103 | * @param {number} [params.navigationWaitTime=30] - 页面导航等待时间(秒) 104 | * @param {number} [params.allWaitTime=30] - 全局等待时间(秒) 105 | * @param {number} [params.maxRetries=3] - 最大重试次数 106 | * @returns {Promise} 初始化完成的实例 107 | * @throws {Error} 如果初始化失败 108 | */ 109 | static async create({ browserId, navigationWaitTime = 30, allWaitTime = 30, maxRetries = 3 }) { 110 | // 创建实例 111 | const instance = new this(browserId); 112 | 113 | // 执行初始化 114 | await instance.start(navigationWaitTime, allWaitTime, maxRetries); 115 | return instance; 116 | } 117 | 118 | /** 119 | * 打开BitBrowser浏览器并获取连接信息 120 | * @private 121 | * @returns {Promise<{ws: string, chromeDriverPath: string, http: string}>} 浏览器连接信息 122 | * @throws {Error} 如果打开浏览器失败 123 | */ 124 | async open() { 125 | try { 126 | 127 | const response = await axios.post(`${bitbrowserUrl}/browser/open`, { id: this.browserId }); 128 | if (response.data.success === true) { 129 | const { ws, driver: chromeDriverPath, http } = response.data.data; 130 | return { ws, chromeDriverPath, http }; 131 | } else { 132 | throw new Error('ws请求失败,请重试'); 133 | } 134 | } catch (error) { 135 | console.error('打开浏览器失败:', error); 136 | throw error; 137 | } 138 | } 139 | 140 | /** 141 | * 启动并初始化浏览器实例 142 | * @private 143 | * @param {number} navigationWaitTime - 页面导航等待时间(秒) 144 | * @param {number} allWaitTime - 全局等待时间(秒) 145 | * @param {number} maxRetries - 最大重试次数 146 | * @throws {Error} 如果初始化失败且达到最大重试次数 147 | */ 148 | async start(navigationWaitTime = 30, allWaitTime = 30, maxRetries = 3) { 149 | let retries = 0; 150 | while (retries < maxRetries) { 151 | try { 152 | if (this.isStarted) { 153 | // console.log('已经调用过start方法,不执行初始化操作'); 154 | return; 155 | } 156 | const { ws, chromeDriverPath, http } = await this.open(); 157 | this.browser = await playwright.chromium.connectOverCDP(ws); 158 | 159 | const allContexts = this.browser.contexts(); 160 | this.context = allContexts[0]; 161 | 162 | const allPages = this.context.pages(); 163 | this.page = allPages[0]; 164 | 165 | // this.defaultWaitTime(this.context, navigationWaitTime, allWaitTime); 166 | 167 | // 设置全屏 168 | // this.browser.maximize(); 169 | 170 | // 关闭其他页面 171 | allPages.forEach(page => { 172 | if (page != this.page) { 173 | page.close(); 174 | } 175 | }); 176 | 177 | // 初始化完毕后设为true,下次调用不会再次初始化 178 | this.isStarted = true; 179 | 180 | // 如果成功初始化,跳出循环 181 | break; 182 | } catch (error) { 183 | console.error('初始化失败,重试中...', error); 184 | retries++; 185 | if (retries >= maxRetries) { 186 | console.error('达到最大重试次数,无法继续初始化。'); 187 | break; 188 | } 189 | // 在重试之前等待一段时间 190 | await sleep(3); // 3秒后重试 191 | } 192 | } 193 | } 194 | 195 | async newContext() { 196 | // Create new context 197 | return await this.browser.newContext() 198 | } 199 | 200 | async newPage(context = '') { 201 | // 创建新的page.不传context就是使用默认的context创建page 202 | if (!context) { context = this.context } 203 | return await context.newPage() 204 | } 205 | 206 | async getPages(context = '') { 207 | // 获取所有页面及长度 208 | if (!context) { context = this.context } 209 | const pages = context.pages() 210 | const pagesCount = pages.length 211 | return { pages, pagesCount } 212 | } 213 | 214 | async isElementExist(selector, { waitTime = 5, page = '' } = {}) { 215 | // 判断元素是否存在 216 | if (!page) { page = this.page } 217 | try { 218 | await page.waitForSelector(selector, { timeout: waitTime * 1000 }) 219 | return true 220 | } catch (error) { 221 | // console.log(error) 222 | return false 223 | } 224 | } 225 | 226 | async isEnabled(selector, { waitTime = 5, page = '' }) { 227 | // 判断元素是否可操作,如点击 228 | if (!page) { page = this.page } 229 | const element = await page.$(selector); 230 | while (true) { 231 | let i = 1 232 | // 等待元素可用(包括可点击) 233 | const isEnabled = await element.isEnabled(); 234 | console.log(isEnabled) 235 | if (isEnabled) { 236 | await element.click() 237 | break 238 | } 239 | await page.waitForTimeout(10000) 240 | // 等待太久退出 241 | i++ 242 | if (i > 8) { break } 243 | } 244 | } 245 | 246 | async pause(page = '') { 247 | // Pause page 248 | if (!page) { page = this.page } 249 | page.pause() 250 | } 251 | 252 | async stop() { 253 | // 关闭浏览器 254 | 255 | // // 这个方法只能关闭playwright自己创建的浏览器,不能关闭连接的浏览器。。。。 256 | // this.browser.close() 257 | 258 | // 用bitbrowser的api关闭浏览器 259 | // const body = {'id': this.browserId} 260 | // body = {'id': this.browserId,'args': [{'openWidth':200000,'openHeight':200000}]} 261 | await axios.post(`${bitbrowserUrl}/browser/close`, { id: this.browserId }); 262 | } 263 | 264 | async closeOtherWindows(context = '') { 265 | // 关闭上下文中的无关页面。 266 | if (!context) { context = this.context } 267 | const allPages = context.pages() 268 | allPages.forEach(page => { 269 | if (page != this.page) { 270 | page.close(); 271 | } 272 | }); 273 | } 274 | } -------------------------------------------------------------------------------- /packages/rpa-module/bitbrowser/phantom.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import 'dotenv/config'; 3 | import { BitBrowserUtil } from './bitbrowser.js'; 4 | import { enCryptText } from '../../crypt-module/crypt.js'; 5 | import { parseToken } from '../../crypt-module/onepassword.js'; 6 | 7 | const phantomID = 'bfnaelmomeimhlpmgjnjophhpkkoljpa' 8 | 9 | export class PhantomUtil extends BitBrowserUtil { 10 | /** 11 | * Phantom钱包工具构造函数 12 | * @param {string} browserId - BitBrowser浏览器ID 13 | */ 14 | constructor(browserId) { 15 | // 调用父类构造函数 16 | super(browserId); 17 | 18 | // 初始化Phantom特有属性 19 | this.homeUrl = `chrome-extension://${phantomID}/index.html#`; 20 | this.unlockUrl = `chrome-extension://${phantomID}/index.html#/account/unlock`; 21 | this.importUrl = `chrome-extension://${phantomID}/onboarding.html`; 22 | } 23 | 24 | /** 25 | * 创建并初始化Phantom钱包工具实例 26 | * @static 27 | * @param {string} browserId - BitBrowser浏览器ID 28 | * @returns {Promise} 初始化完成的实例 29 | * @throws {Error} 如果初始化失败 30 | */ 31 | static async create({ browserId }) { 32 | // 创建实例 33 | const instance = await super.create({ browserId }); 34 | 35 | return instance; 36 | } 37 | 38 | async createNewWallet(indexId, walletfile = 'solWallets.csv'){ 39 | if(!process.env.PHANTOMPASSWORD) { 40 | process.env.PHANTOMPASSWORD = await parseToken(process.env.phantomPassword); 41 | } 42 | await this.page.goto(this.importUrl) 43 | await this.page.waitForTimeout(1000) 44 | await this.page.getByTestId('create-wallet-button').click() 45 | await this.page.waitForTimeout(1000) 46 | try{ 47 | await this.page.getByTestId('onboarding-form-password-input').fill(process.env.PHANTOMPASSWORD) 48 | await this.page.waitForTimeout(500) 49 | await this.page.getByTestId('onboarding-form-confirm-password-input').fill(process.env.PHANTOMPASSWORD) 50 | await this.page.waitForTimeout(500) 51 | const status = await this.page.getByTestId('onboarding-form-terms-of-service-checkbox').isChecked() 52 | if(!status){ 53 | await this.page.getByTestId('onboarding-form-terms-of-service-checkbox').click() 54 | } 55 | await this.page.waitForTimeout(500) 56 | await this.page.getByTestId('onboarding-form-submit-button').click() 57 | await this.page.waitForTimeout(1000) 58 | }catch(error){} 59 | 60 | await this.page.waitForTimeout(1000) 61 | // 获取屏幕的宽度和高度 62 | const screenWidth = await this.page.evaluate(() => window.screen.width); 63 | const screenHeight = await this.page.evaluate(() => window.screen.height); 64 | // 计算屏幕中央的坐标 65 | const centerX = Math.floor(screenWidth / 2); 66 | const centerY = Math.floor(screenHeight / 2); 67 | await this.page.mouse.move(centerX, centerY); 68 | await this.page.waitForTimeout(1000) 69 | 70 | const mnemonic1 = await this.page.getByTestId('secret-recovery-phrase-word-input-0').getAttribute('value') 71 | const mnemonic2 = await this.page.getByTestId('secret-recovery-phrase-word-input-1').getAttribute('value') 72 | const mnemonic3 = await this.page.getByTestId('secret-recovery-phrase-word-input-2').getAttribute('value') 73 | const mnemonic4 = await this.page.getByTestId('secret-recovery-phrase-word-input-3').getAttribute('value') 74 | const mnemonic5 = await this.page.getByTestId('secret-recovery-phrase-word-input-4').getAttribute('value') 75 | const mnemonic6 = await this.page.getByTestId('secret-recovery-phrase-word-input-5').getAttribute('value') 76 | const mnemonic7 = await this.page.getByTestId('secret-recovery-phrase-word-input-6').getAttribute('value') 77 | const mnemonic8 = await this.page.getByTestId('secret-recovery-phrase-word-input-7').getAttribute('value') 78 | const mnemonic9 = await this.page.getByTestId('secret-recovery-phrase-word-input-8').getAttribute('value') 79 | const mnemonic10 = await this.page.getByTestId('secret-recovery-phrase-word-input-9').getAttribute('value') 80 | const mnemonic11 = await this.page.getByTestId('secret-recovery-phrase-word-input-10').getAttribute('value') 81 | const mnemonic12 = await this.page.getByTestId('secret-recovery-phrase-word-input-11').getAttribute('value') 82 | 83 | const mnemonic = mnemonic1 + ' ' + mnemonic2 + ' ' + mnemonic3 + ' ' + mnemonic4 + ' ' + mnemonic5 + ' ' + mnemonic6 + ' ' + mnemonic7 + ' ' + mnemonic8 + ' ' + mnemonic9 + ' ' + mnemonic10 + ' ' + mnemonic11 + ' ' + mnemonic12 84 | // console.log(mnemonic) 85 | const enSolMnemonic = await enCryptText(mnemonic) 86 | const status = await this.page.getByTestId('onboarding-form-saved-secret-recovery-phrase-checkbox').isChecked() 87 | if(!status){ 88 | await this.page.getByTestId('onboarding-form-saved-secret-recovery-phrase-checkbox').click() 89 | } 90 | await this.page.getByTestId('onboarding-form-submit-button').click() 91 | await this.page.waitForTimeout(1000) 92 | await this.page.getByTestId('onboarding-form-submit-button').click() 93 | 94 | // 判断文件是否存在 95 | if (!fs.existsSync(walletfile)) { 96 | // 文件不存在则创建文件并写入标题行 97 | const header = 'index_id,enSolMnemonic\n'; 98 | fs.writeFileSync(walletfile, header); 99 | } 100 | const file = fs.openSync(walletfile, 'a'); 101 | const rowData = `${indexId},${enSolMnemonic}\n` 102 | // 文件存在则追加,不存在则创建 103 | fs.appendFileSync(file, rowData); 104 | 105 | } 106 | 107 | // async connectWallet(url,{hasConnectButton=true, connectButton='text=/(Connect|Connect Wallet|Connect to Wallet|连接钱包|Login)/i', hasCheckButton=false, checkButton='text=/(Phantom)/i', waitTime=3}={}){ 108 | // if(!process.env.PHANTOMPASSWORD) { 109 | // process.env.PHANTOMPASSWORD = await parseToken(process.env.phantomPassword); 110 | // } 111 | // await this.page.goto(url) 112 | // if(hasConnectButton){ 113 | // try{ 114 | // await this.page.waitForTimeout(500) 115 | // await this.page.waitForSelector(connectButton, {timeout:10000}).then(element => { element.click() }); 116 | // await this.page.waitForTimeout(500) 117 | // }catch(error){console.log(error)} 118 | // } 119 | // // 有些应用还需要先点一下checkbox才能选钱包。。。 120 | // if(hasCheckButton){ 121 | // try{ 122 | // await this.page.waitForTimeout(500) 123 | // await this.page.waitForSelector(checkButton, {timeout:6000}).then(element => { element.click() }); 124 | // await this.page.waitForTimeout(500) 125 | // }catch(error){console.log(error)} 126 | // } 127 | 128 | // let status = await this.changeHandle() 129 | // // console.log(`status: ${status}`) 130 | // if(status){ 131 | // try{ 132 | // await this.driver.wait(until.elementLocated(By.xpath('//form[@id="unlock-form"]/div/div[2]/div/input')), 5000) 133 | // .then(element => this.driver.wait(until.elementIsVisible(element), 5000)) 134 | // .then(element => element.sendKeys(process.env.PHANTOMPASSWORD)); 135 | // await this.driver.wait(until.elementLocated(By.xpath('//button[text()="解锁"]')), 5000) 136 | // .then(element => this.driver.wait(until.elementIsVisible(element), 5000)) 137 | // .then(element => element.click()); 138 | // }catch{} 139 | // try{ 140 | // await this.driver.wait(until.elementLocated(By.xpath('//button[text()="连接"]')), 5000) 141 | // .then(element => this.driver.wait(until.elementIsVisible(element), 5000)) 142 | // .then(element => element.click()); 143 | // }catch{} 144 | // } 145 | 146 | // } 147 | 148 | async executeTransaction(selector, { page='', isElementhadle=false, isConfirmPage=false, confirmButton='text=/(^Confirm Swap%$)/', canEditGas=true, gasLimitRate=0.5 }={}) { 149 | try{ 150 | if (!page){ page = this.page } 151 | try{ 152 | await page.waitForTimeout(3000) 153 | if(isElementhadle){ 154 | await selector.click() 155 | }else{ 156 | await page.waitForSelector(selector, {timeout:10000}).then(element => { element.click() }); 157 | } 158 | await page.waitForTimeout(2000) 159 | }catch(error){console.log(error)} 160 | 161 | // 有些应用会多一个确认页面 162 | if (isConfirmPage) { 163 | try{ 164 | await page.waitForSelector('text=/(^Accept%$)/', {timeout:1000}).then(element => { element.click() }); 165 | }catch(error){} 166 | try{ 167 | await page.waitForTimeout(2000) 168 | await page.waitForSelector(confirmButton, {timeout:5000}).then(element => { element.click() }); 169 | }catch(error){console.log(error)} 170 | } 171 | await page.waitForTimeout(5000) 172 | 173 | const element = await this.argentXPage.$('text=/(^Confirm$)/'); 174 | while(true){ 175 | let i = 1 176 | // 等待元素可用(包括可点击) 177 | const isEnabled = await element.isEnabled(); 178 | console.log(isEnabled) 179 | if(isEnabled){ 180 | await element.click() 181 | break 182 | } 183 | await this.argentXPage.waitForTimeout(10000) 184 | // 等待太久退出 185 | i++ 186 | if(i > 8){break} 187 | } 188 | // 将page页面带到前台 189 | await page.bringToFront() 190 | // 等待确认 191 | await page.waitForTimeout(10000) 192 | }catch(error){console.log(error)} 193 | } 194 | } -------------------------------------------------------------------------------- /packages/rpa-module/browserConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 浏览器环境配置文件。支持本地浏览器环境和指纹浏览器环境 3 | * 提供统一的浏览器配置,供不同模块共享使用 4 | */ 5 | 6 | import { ChromeBrowserUtil } from './chrome/chromeBrowser/chromeBrowser.js'; 7 | import { BitBrowserUtil } from './bitbrowser/bitbrowser.js'; 8 | 9 | /** 10 | * 各种浏览器环境的统一配置 11 | * 用于创建不同类型的浏览器实例 12 | */ 13 | export const browserConfigs = { 14 | chrome: { 15 | baseClass: ChromeBrowserUtil, 16 | createParams: (browserId) => ({ chromeNumber: browserId }) 17 | }, 18 | bitbrowser: { 19 | baseClass: BitBrowserUtil, 20 | createParams: (browserId) => ({ browserId }) 21 | } 22 | }; 23 | 24 | /** 25 | * 创建浏览器工具实例的通用函数 26 | * @param {Object} options - 创建选项 27 | * @param {string} [options.browserType='chrome'] - 浏览器类型,'chrome'或'bitbrowser' 28 | * @param {number|string} options.browserId - Chrome实例编号或BitBrowser浏览器ID 29 | * @param {Object} [options.additionalParams={}] - 其他参数 30 | * @returns {Promise} 创建的浏览器实例 31 | */ 32 | export async function createBrowserUtil({ browserType = 'chrome', browserId, additionalParams = {} }) { 33 | // 1. 获取浏览器配置 34 | const config = browserConfigs[browserType.toLowerCase()]; 35 | if (!config) { 36 | throw new Error(`不支持的浏览器类型: ${browserType}`); 37 | } 38 | 39 | // 2. 创建浏览器参数 40 | const params = { 41 | ...config.createParams(browserId), 42 | ...additionalParams 43 | }; 44 | 45 | // 3. 创建浏览器实例 46 | return await config.baseClass.create(params); 47 | } -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/README.md: -------------------------------------------------------------------------------- 1 | # Chrome多开管理系统 2 | 3 | 基于Node.js的Chrome多实例管理解决方案,支持独立配置、指纹隔离和代理服务。 4 | 5 | >注意:只适用于mac系统,环境是Apple M2 Max Ventura 13.6.2。其他环境未测试。 6 | 7 | ## 主要功能 8 | ✅ 多实例管理 - 同时运行多个独立Chrome实例 9 | ✅ 指纹隔离 - 每个实例拥有独特浏览器指纹 10 | ✅ 代理集成 - 自动绑定独立代理服务器 11 | ✅ 自动化部署 - 一键创建/删除实例 12 | 13 | 14 | ### config.js 15 | 16 | 此文件是配置文件,配置了chrome的应用安装路径、chrome可执行文件路径、指纹文件路径、监听端口、代理端口等。 17 | 18 | ### 多实例管理 19 | 20 | 多个独立Chrome实例,每个实例一套用户数据,拥有独立的指纹和代理配置。 21 | 22 | #### 添加实例 23 | 24 | 创建chrome应用、指纹文件。 25 | ```js 26 | import { ChromeAutomation } from './chromeProfile.js'; 27 | 28 | // 创建Chrome用户 参数代表第几个账号 29 | const chromeAutomation = new ChromeAutomation(1); 30 | await chromeAutomation.createChromeProfile(); 31 | ``` 32 | 33 | #### 打开实例 34 | 35 | ```js 36 | import { ChromeBrowserUtil } from './chromeBrowser.js'; 37 | 38 | // 参数:实例编号 39 | const chrome = new ChromeBrowserUtil.create({ chromeNumber : 1 }); 40 | ``` 41 | 42 | #### chrome应用图标 43 | chrome图标一样的话,多实例管理不好区分。 下面这个教程可以修改图标,让每个图标编号都不一样,比如chrome1、chrome2。`image`目录下是我做好的一些图标,可以直接用。 44 | - 手动修改应用图标:https://blog.csdn.net/tekin_cn/article/details/140003742 45 | - 图标不规则不好看?手把手教你绘制苹果官方圆角图标:https://www.zhihu.com/zvideo/1656756405129486337?utm_id=0 46 | 47 | 1、生成带数字的chrome图标 48 | ```js 49 | import { generateNumberedChromeIcon } from './chromeProfile.js'; 50 | 51 | // 参数1: chromeNumber = 1,实例编号 参数2: savePath = 'image/icons/png'图标保存路径 52 | await generateNumberedChromeIcon(1); 53 | ``` 54 | 55 | 2、使用`Image2Icon`工具将png图片转换为icons图标 56 | 57 | 3、找到应用图标,右键选择 显示简介,复制上一步生成的icons图标,简介页面点击图标,替换chrome应用图标 58 | 59 | ![dock显示栏](https://raw.githubusercontent.com/gaohongxiang/images/master/编程/google/dock显示栏.jpg) 60 | 61 | #### chrome网页图标 62 | 63 | 方案1是通过修改内置头像间接显示chrome编号。缺点是内置头像只有56个,浏览器实例超出数量的话就没法继续编号了 64 | 65 | 方案2通过启动一个自定义网页来显示chrome编号。缺点是每次启动浏览器都要多打开一个网页 66 | 67 | ##### 最终方案,登录chrome账号,然后替换头像 68 | 1、登录chrome账号,系统会在数据目录下生成一个`Google Profile Picture.png`图片。 69 | 70 | 2、生成一个数字图片替换上一步的图片 71 | ```js 72 | import { ChromeAutomation } from './chromeProfile.js'; 73 | 74 | // 参数1:实例编号 75 | const chromeAutomation = new ChromeAutomation(1); 76 | await chromeAutomation.replaceAvatar(); 77 | ``` 78 | 79 | ![网页图标](https://raw.githubusercontent.com/gaohongxiang/images/master/编程/google/网页图标.jpg) 80 | 81 | #### 插件 82 | 83 | 目前方案是使用rpa下载插件 84 | 85 | ```js 86 | import { ChromeBrowserUtil } from './chromeBrowser.js'; 87 | 88 | // 参数:实例编号 89 | const chrome = new ChromeBrowserUtil.create({ chromeNumber : 1 }); 90 | // 安装okx插件 91 | await chrome.installExtension('https://chromewebstore.google.com/detail/%E6%AC%A7%E6%98%93-web3-%E9%92%B1%E5%8C%85/mcohilncbfahbmgdjkbpemcciiolgcge'); 92 | ``` 93 | 94 | #### 退出实例 95 | mac版chrome手动关闭浏览器无法完全退出,比较麻烦。退出有3种方式 96 | - command + q 退出 97 | - 右键点击dock图标 退出 98 | - 脚本 退出 99 | - 参考:如何停止谷歌Chrome在后台运行:https://www.chrome64.com/skill/729.html 100 | 101 | ```js 102 | import { shutdownChrome } from './chromeBrowser.js'; 103 | 104 | shutdownChrome(1); 105 | ``` 106 | 107 | ### 指纹隔离 108 | 109 | 指纹是创建多实例时同步创建的,使用的是`fingerprint-generator`库,指纹文件在各实例数据目录下`fingerprint.json`。 110 | 111 | 使用时是通过`fingerprint-injector`库直接注入浏览器里的 112 | 113 | ### 代理集成 114 | 115 | 买的代理一般是带认证的socks5代理,chrome不支持socks5认证,所以需要本地中转一下,浏览器启动时添加了参数 `--proxy-server=127.0.0.1:listenPort`,使用代理服务器转发流量到socks代理处理。 116 | 117 | ``` 118 | 浏览器 -> 代理服务器 -> SOCKS5代理 -> 目标网站 119 | 浏览器 -> localhost:20001 -> 67.100.105.107:7686 -> 目标网站 120 | ``` 121 | 122 | #### 开始代理服务 123 | 124 | package.json配置了代理管理器 125 | ```js 126 | "bin": { 127 | "proxy-manager": "./packages/rpa-module/chrome/chromeBrowser/proxyManger.js" 128 | } 129 | ``` 130 | 131 | 第一次使用先执行以下命令 132 | 133 | ```shell 134 | # 确保在项目根目录下 135 | cd 你的路径/web3-script 136 | 137 | # 链接 138 | sudo npm link 139 | 140 | # 检查链接(看看路径对不对) 141 | which proxy-manager 142 | 143 | # 检查文件权限 144 | ls -l packages/rpa-module/chrome/chromeBrowser/proxyManger.js 145 | 146 | # 如果需要,添加执行权限 147 | chmod +x packages/rpa-module/chrome/chromeBrowser/proxyManger.js 148 | ``` 149 | 150 | 后续使用可以单独使用一个终端来执行以下命令 151 | ```shell 152 | # 逗号连接的表示范围,比如3,5表示3、4、5 153 | proxy-manager start 1,10 # 开启10个代理服务 154 | proxy-manager start 1 2 3,5 # 开启5个代理服务,1、2、3、4、5 155 | ``` 156 | 157 | 启动信息 158 | ``` 159 | [2025-03-07_20:15:12] 代理服务器启动 [Chrome 1] [端口 20001] [ip xx.xx.xx.xx] 160 | ``` 161 | 162 | #### 停止代理服务 163 | 164 | 停止代理服务直接 ctrl + c 即可 165 | 166 | 167 | ### 一些常用命令 168 | ```shell 169 | # 查看端口进程 170 | sudo lsof -t -i :端口号 171 | 172 | # 强制解除端口占用 173 | sudo kill -9 $(sudo lsof -t -i :端口号) 174 | 175 | # 获取浏览器版本 176 | curl -s http://localhost:端口号/json/version | jq .webSocketDebuggerUrl 177 | ``` 178 | 179 | ## 快速开始 180 | 181 | 1. 创建多个chrome实例 182 | 2. 修改chrome图标 183 | 3. 启动代理服务 184 | 4. 启动浏览器 185 | 5. 登录chrome账号,替换头像 186 | 6. 安装插件 187 | 7. 关闭浏览器 188 | 8. 停止代理服务 189 | 190 | 191 | 192 | ## 参考 193 | 194 | - 资源汇总:https://github.com/zhaotoday/fingerprint-browser?tab=readme-ov-file 195 | - 开源的谷歌多开管理器 196 | - Chrome Power:https://github.com/zmzimpl/chrome-power-app 197 | - VirtualBrowser:https://github.com/Virtual-Browser/VirtualBrowser 198 | - toolBoxClient:https://github.com/web3ToolBoxDev/toolBoxClient 199 | - XChrome:https://github.com/chanawudi/XChrome/?tab=readme-ov-file 200 | - 使用 NodeJS 实现 IP 和指纹独立的 Chrome 多开管理程序 201 | - https://blog.ulsincere.com/multiple-chrome 202 | - https://github.com/zmzimpl/chrome-power-chromium 203 | - 撸毛群控:https://github.com/fabius8/chromeAuto 204 | - mac多开chrome(多应用):https://juejin.cn/post/7370895432567816242 205 | - MacOS系统 多开 Google Chrome(同应用不同用户数据):https://www.youtube.com/watch?v=UHCao7JhH-A 206 | - MacOS系统 多开 Google Chrome:https://x.com/dnvvgjyp/status/1793845106656890962 207 | - MacOS系统 多开 Google Chrome:https://x.com/ariel_sands_dan/status/1816498255792058394 208 | - MacOS系统 多开 Google Chrome:https://x.com/necaluo/status/1785214239793438774 209 | - Mac关闭和禁止Chrome自动更新方法:https://juejin.cn/post/7411187555776118819 210 | - 如何使用Chrome浏览器,打包生成自己的插件(crx格式文件):https://www.cnblogs.com/Galesaur-wcy/p/15748799.html 211 | - 同步器(目前只有windows版):https://github.com/devilflasher/Chrome-Manager 212 | 213 | 检测指纹网站: 214 | - https://zhuanlan.zhihu.com/p/654468171 215 | - https://www.browserscan.net/ 216 | - https://fingerprintjs.github.io/fingerprintjs/ 217 | 218 | -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export const BASE_CONFIG = { 4 | // Chrome应用路径 5 | CHROME_PATH: '/Applications/Google Chrome.app', 6 | CHROME_MULTI_DIR: '/Applications/Chrome多开', 7 | 8 | // 数据目录 9 | DATA_DIR: path.join(process.env.HOME, 'Chrome多开'), 10 | 11 | // 端口配置 12 | DEBUG_BASE_PORT: 10000, // 调试端口,playwright使用 13 | LISTEN_BASE_PORT: 20000, // 监听端口,代理使用 14 | 15 | // 获取Chrome实例路径 16 | getChromeInstancePath: (chromeId) => { 17 | return path.join(BASE_CONFIG.CHROME_MULTI_DIR, `Chrome${chromeId}.app`); 18 | }, 19 | 20 | // 获取Chrome可执行文件路径 21 | getChromeExecutable: (chromeId) => { 22 | return path.join(BASE_CONFIG.getChromeInstancePath(chromeId), 'Contents/MacOS/Google Chrome'); 23 | }, 24 | 25 | // 获取Chrome数据目录 26 | getProfileDataDir: (chromeId) => { 27 | return path.join(BASE_CONFIG.DATA_DIR, `Chrome${chromeId}`); 28 | }, 29 | 30 | // 获取Chrome默认目录 31 | getChromeDefaultDir: (chromeId) => { 32 | return path.join(BASE_CONFIG.getProfileDataDir(chromeId), 'Default'); 33 | }, 34 | 35 | // 获取指纹文件路径 36 | getFingerprintPath: (chromeId) => { 37 | return path.join(BASE_CONFIG.getProfileDataDir(chromeId), 'fingerprint.json'); 38 | }, 39 | 40 | // 获取调试端口 41 | getDebugPort: (chromeId) => { 42 | return BASE_CONFIG.DEBUG_BASE_PORT + parseInt(chromeId); 43 | }, 44 | 45 | // 获取监听端口 46 | getListenPort: (chromeId) => { 47 | return BASE_CONFIG.LISTEN_BASE_PORT + parseInt(chromeId); 48 | } 49 | }; -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome001.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome002.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome003.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome004.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome005.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome006.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome007.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome008.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome009.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome010.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome011.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome012.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome013.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome014.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome015.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome016.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome017.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome018.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome019.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome020.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome021.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome022.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome023.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome024.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome025.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome026.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome027.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome028.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome029.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome030.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome031.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome032.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome033.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome034.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome035.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome036.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome037.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome038.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome039.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome040.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome041.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome042.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome043.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome044.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome044.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome045.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome045.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome046.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome047.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome048.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome049.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome049.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/avatar/Chrome050.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome001.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome001.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome002.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome002.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome003.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome003.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome004.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome004.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome005.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome005.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome006.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome006.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome007.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome007.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome008.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome008.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome009.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome009.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome010.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome010.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome011.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome011.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome012.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome012.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome013.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome013.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome014.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome014.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome015.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome015.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome016.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome016.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome017.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome017.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome018.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome018.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome019.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome019.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome020.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome020.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome021.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome021.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome022.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome022.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome023.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome023.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome024.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome024.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome025.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome025.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome026.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome026.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome027.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome027.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome028.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome028.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome029.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome029.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome030.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome030.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome031.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome031.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome032.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome032.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome033.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome033.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome034.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome034.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome035.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome035.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome036.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome036.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome037.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome037.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome038.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome038.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome039.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome039.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome040.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome040.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome041.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome041.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome042.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome042.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome043.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome043.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome044.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome044.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome045.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome045.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome046.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome046.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome047.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome047.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome048.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome048.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome049.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome049.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome050.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/icons/chrome050.icns -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome001.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome002.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome003.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome004.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome005.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome006.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome007.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome008.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome009.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome010.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome011.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome012.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome013.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome014.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome015.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome016.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome017.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome018.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome019.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome020.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome021.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome022.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome023.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome024.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome025.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome026.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome027.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome028.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome029.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome030.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome031.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome032.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome033.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome034.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome035.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome036.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome037.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome038.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome039.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome040.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome041.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome042.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome043.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome044.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome044.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome045.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome045.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome046.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome047.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome048.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome049.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome049.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaohongxiang/web3-script/6d2c79531b762c8bd1717bfd895761b7b1f6cb26/packages/rpa-module/chrome/chromeBrowser/image/icons/png/chrome050.png -------------------------------------------------------------------------------- /packages/rpa-module/chrome/chromeBrowser/proxyManger.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { ProxyServer } from './proxyServer.js'; 4 | import { myFormatData } from '../../../utils-module/formatdata.js'; 5 | import { BASE_CONFIG } from './config.js'; 6 | import minimist from 'minimist'; 7 | import { notificationManager } from '../../../notification-module/notification.js'; 8 | 9 | // 启动代理 10 | const startProxy = async (...inputs) => { 11 | try { 12 | const data = await myFormatData(...inputs); 13 | for (const d of data) { 14 | const listenPort = BASE_CONFIG.getListenPort(d['indexId']); 15 | const proxyServer = new ProxyServer({ listenPort, socksProxyUrl: d['socksProxyUrl'], chromeNumber: d['indexId'] }); 16 | await proxyServer.start(); 17 | } 18 | } catch (error) { 19 | notificationManager.error(`代理运行失败: ${error.message}`); 20 | } 21 | }; 22 | 23 | // 停止代理 24 | const stopProxy = async (...inputs) => { 25 | try { 26 | const data = await myFormatData(...inputs); 27 | for (const d of data) { 28 | const listenPort = BASE_CONFIG.getListenPort(d['indexId']); 29 | const proxyServer = new ProxyServer({ listenPort, socksProxyUrl: d['socksProxyUrl'], chromeNumber: d['indexId'] }); 30 | await proxyServer.shutdown(); 31 | } 32 | } catch (error) { 33 | notificationManager.error(`停止代理失败: ${error.message}`); 34 | } 35 | }; 36 | 37 | // 主函数 38 | const main = async () => { 39 | // 解析命令行参数 40 | const argv = minimist(process.argv.slice(2)); 41 | const command = argv._[0]; // 获取命令(start/stop) 42 | const instances = argv._.slice(1); // 获取实例编号参数 43 | 44 | if (!command || instances.length === 0) { 45 | console.log('使用方法: '); 46 | console.log(' 启动代理: proxy-manager start 1 2 3,5'); 47 | console.log(' 停止代理: proxy-manager stop 1 2 3,5'); 48 | return; 49 | } 50 | 51 | switch (command) { 52 | case 'start': 53 | await startProxy(...instances); 54 | break; 55 | case 'stop': 56 | await stopProxy(...instances); 57 | break; 58 | default: 59 | console.log('未知命令,请使用 start 或 stop'); 60 | } 61 | }; 62 | 63 | // 运行主函数 64 | main().catch(error => { 65 | notificationManager.error(`代理管理器运行错误: ${error.message}`); 66 | process.exit(1); 67 | }); -------------------------------------------------------------------------------- /packages/social-module/email/README.md: -------------------------------------------------------------------------------- 1 | ## 邮箱脚本 2 | 3 | ## Gmail OAuth 2.0 配置指南 4 | 5 | ### 1. 创建和配置应用 6 | 7 | #### 1.1 创建应用 8 | 1. 访问 [Google Cloud Console](https://console.cloud.google.com) 9 | 2. 使用你的 Google 账号登录 10 | 3. 创建新项目或选择现有项目 11 | 4. 在左侧菜单找到"API和服务" → "OAuth 同意屏幕" 12 | 5. 选择用户类型: 13 | - 选择"外部",允许任何 Google 账户访问 14 | - 点击"创建" 15 | 6. 配置 OAuth 同意屏幕: 16 | - 应用名称:自定义一个应用名称 17 | - 用户支持电子邮件:选择你的邮箱 18 | - 开发者联系信息:填写你的邮箱 19 | - 点击"保存并继续" 20 | 21 | #### 1.2 配置身份验证 22 | 1. 在左侧菜单点击"凭据"(Credentials) 23 | 2. 点击"创建凭据" → "OAuth 客户端 ID" 24 | 3. 选择应用类型为"Web 应用程序" 25 | 4. 填写基本信息: 26 | - 名称:自定义一个名称 27 | - 已获授权的重定向 URI:添加 `https://mail.google.com` 28 | 5. 点击"创建"完成设置 29 | 30 | #### 1.3 配置 API 权限 31 | 1. 在左侧菜单点击"API 和服务" 32 | 2. 搜索并启用以下 API: 33 | - Gmail API 34 | 35 | #### 1.4 获取应用凭据 36 | 1. 在"凭据"页面找到刚创建的 OAuth 2.0 客户端 37 | 2. 下载 JSON 文件或记录以下信息: 38 | - 客户端 ID (Client ID) 39 | - 客户端密钥 (Client Secret) 40 | - 重定向 URI (Redirect URI) 41 | 42 | ### 2. 环境变量配置 43 | 44 | 在项目的 `.env` 文件中添加以下配置: 45 | 46 | ```bash 47 | # Gmail OAuth 配置 48 | gmailClientId = '你的客户端ID' 49 | gmailClientSecret = '你的客户端密钥' 50 | gmailRedirectUri = 'https://mail.google.com' 51 | ``` 52 | 53 | ### 3. 注意事项 54 | 55 | 1. 一个应用(未审核)最多只能添加100个用户(邮箱),如果需要添加更多用户,需要创建多个应用。 56 | 57 | 1. 重定向 URI 配置: 58 | - 必须完全匹配 Google Cloud Console 中的配置 59 | 60 | 2. 权限要求: 61 | - 首次授权时需要用户明确同意所有请求的权限 62 | - 确保请求了 offline_access 以获取 refresh_token 63 | - 只请求应用必需的最小权限集 64 | 65 | 3. 安全性: 66 | - 不要在代码中硬编码 client_secret 67 | - 保护好凭据 JSON 文件和 .env 文件 68 | - 定期更新客户端密钥 69 | 70 | 4. 常见问题: 71 | - 如果无法获取 refresh_token,确保: 72 | * 应用配置为 Web 应用程序类型 73 | * 请求中包含 access_type=offline 74 | * 用户完成了授权流程 75 | - 授权失败时: 76 | * 检查重定向 URI 是否正确 77 | * 验证客户端 ID 和密钥 78 | * 查看 OAuth 同意屏幕是否正确配置 79 | 80 | ### 4. 相关文档 81 | 82 | - [Gmail API 文档](https://developers.google.com/gmail/api/guides) 83 | - [Google OAuth 2.0 文档](https://developers.google.com/identity/protocols/oauth2) 84 | - [Node.js Gmail API 快速入门](https://developers.google.com/gmail/api/quickstart/nodejs) 85 | 86 | 87 | ## Outlook OAuth 2.0 配置指南 88 | 89 | ### 1. 创建和配置应用 90 | 91 | #### 1.1 创建应用 92 | 1. 访问 [Microsoft Entra 管理中心](https://entra.microsoft.com) 93 | 2. 使用你的 Microsoft 账号登录 94 | 3. 在左侧菜单找到"应用注册"(App registrations) 95 | 4. 点击"新注册"(New registration)按钮 96 | 5. 填写应用信息: 97 | - 名称:自定义一个应用名称 98 | - 支持的账户类型:选择"任何组织目录中的账户和个人 Microsoft 帐户" 99 | - 重定向 URI:选择"Web",输入 `http://localhost:3000/auth/redirect` 100 | 6. 点击"注册"完成创建 101 | 102 | #### 1.2 配置身份验证 103 | 1. 在应用页面左侧菜单点击"身份验证"(Authentication) 104 | 2. 在"平台配置"下确认已添加 Web 平台 105 | 3. 确保重定向 URI 正确:`http://localhost:3000/auth/redirect` 106 | 5. 点击"保存" 107 | 108 | #### 1.3 配置 API 权限 109 | 1. 在左侧菜单点击"API 权限"(API permissions) 110 | 2. 点击"添加权限"(Add a permission) 111 | 3. 选择 "Microsoft Graph" 112 | 4. 选择"委托的权限"(Delegated permissions) 113 | 5. 搜索并添加以下权限: 114 | - offline_access (必需,用于获取 refresh token) 115 | - openid 116 | - profile 117 | - Mail.Read 118 | - Mail.Send 119 | - User.Read 120 | 6. 点击"代表 xxx 授予管理员同意"按钮 121 | 122 | #### 1.4 获取应用凭据 123 | 1. 在左侧菜单点击"证书和密码"(Certificates & secrets) 124 | 2. 在"客户端密码"(Client secrets)部分: 125 | - 点击"新建客户端密码" 126 | - 选择合适的过期时间(建议1年或2年) 127 | - 点击"添加" 128 | 3. 立即复制生成的密码值(仅显示一次!) 129 | 4. 记录以下信息: 130 | - 应用程序(客户端) ID:从应用概述页面获取 131 | - 客户端密码:刚才生成的密码值 132 | - 租户 ID:从应用概述页面获取(可选) 133 | 134 | ### 2. 环境变量配置 135 | 136 | 在项目的 `.env` 文件中添加以下配置: 137 | 138 | ```bash 139 | # Outlook OAuth 配置 140 | outlookClientId = '你的应用程序ID' 141 | outlookClientSecret = '你的客户端密码' 142 | outlookRedirectUri = 'http://localhost:3000/auth/redirect' 143 | ``` 144 | 145 | ### 3. 注意事项 146 | 147 | 1. 重定向 URI 必须完全匹配: 148 | - Azure 门户中的配置 149 | - 代码中的 REDIRECT_URI 150 | - .env 文件中的配置 151 | 152 | 2. 权限要求: 153 | - 确保所有必需的权限都已获得管理员同意 154 | - offline_access 权限是获取 refresh_token 的必要条件 155 | 156 | 3. 安全性: 157 | - 不要在代码中硬编码 client_secret 158 | - 保护好 .env 文件 159 | - 定期更新客户端密码 160 | 161 | 4. 常见问题: 162 | - 如果无法获取 refresh_token,检查是否已授权 offline_access 权限 163 | - 授权失败时,检查重定向 URI 是否配置正确 164 | - 确保使用了正确的应用类型(Web 应用程序) 165 | 166 | ### 4. 相关文档 167 | 168 | - [Microsoft 身份平台文档](https://learn.microsoft.com/zh-cn/entra/identity-platform/quickstart-web-app-nodejs-sign-in) 169 | - [Graph API 文档](https://learn.microsoft.com/zh-cn/graph/api/overview) 170 | 171 | 172 | ## 使用示例 173 | 174 | ```js 175 | import { GmailAuthenticator, waitForGmailVerificationCode, GmailRpa } from './gmail.js'; 176 | import { OutlookAuthenticator, waitForOutlookVerificationCode } from './outlook.js'; 177 | 178 | // Gmail授权示例 179 | const gmailAuth = await GmailAuthenticator.create({ 180 | browserType: 'chrome', // 浏览器类型:'chrome'或'bitbrowser' 181 | browserId: 1, // Chrome实例编号或BitBrowser浏览器ID 182 | socksProxyUrl: 'socks5://username:password@ip:port' // 代理地址 183 | }); 184 | await gmailAuth.authorizeAndSaveToken({ 185 | csvFile: './data/social/email.csv', 186 | matchField: 'email', 187 | matchValue: 'xxx@gmail.com', 188 | targetField: 'gmailRefreshToken' 189 | }); 190 | 191 | // 获取Gmail验证码 192 | const gmailVerifyCode = await waitForGmailVerificationCode({ 193 | refreshToken: 'gmail刷新令牌', 194 | socksProxyUrl: 'socks5://username:password@ip:port', 195 | from: '发送方邮箱', 196 | subject: '验证码', 197 | pollInterval: 10, // 可选,轮询间隔(秒) 198 | timeout: 300, // 可选,总超时时间(秒) 199 | recentMinutes: 5 // 可选,查询最近几分钟内的邮件 200 | }); 201 | console.log(gmailVerifyCode); 202 | 203 | // Gmail RPA操作示例 204 | const gmailRpa = await GmailRpa.create({ 205 | browserType: 'chrome', // 浏览器类型:'chrome'或'bitbrowser' 206 | browserId: 1 // Chrome实例编号或BitBrowser浏览器ID 207 | }); 208 | 209 | // 登录Gmail账号 210 | await gmailRpa.login('username@gmail.com', 'password', 'otpSecretKey'); 211 | 212 | // 切换界面语言为中文 213 | await gmailRpa.changeLanguage(); 214 | 215 | // 设置两步验证 216 | await gmailRpa.addOrChange2fa({ 217 | password: 'yourPassword', 218 | matchValue: 'username@gmail.com' 219 | }); 220 | 221 | 222 | // Outlook授权示例(需要指纹浏览器配合) 223 | const outlookAuth = await OutlookAuthenticator.create({ 224 | browserType: 'chrome', // 浏览器类型:'chrome'或'bitbrowser' 225 | browserId: 1, // Chrome实例编号或BitBrowser浏览器ID 226 | socksProxyUrl: 'socks5://username:password@ip:port' // 代理地址 227 | }); 228 | await outlookAuth.authorizeAndSaveToken({ 229 | csvFile: './data/social/email.csv', 230 | matchField: 'email', 231 | matchValue: 'xxx@outlook.com', 232 | targetField: 'outlookRefreshToken' 233 | }); 234 | 235 | // 获取Outlook验证码 236 | const outlookVerifyCode = await waitForOutlookVerificationCode({ 237 | refreshToken: 'outlook刷新令牌', 238 | socksProxyUrl: 'socks5://username:password@ip:port', 239 | from: '发送方邮箱', 240 | subject: '验证码', 241 | pollInterval: 10, // 可选,轮询间隔(秒) 242 | timeout: 300, // 可选,总超时时间(秒) 243 | recentMinutes: 5 // 可选,查询最近几分钟内的邮件 244 | }); 245 | console.log(outlookVerifyCode); 246 | ``` 247 | -------------------------------------------------------------------------------- /packages/social-module/x/README.md: -------------------------------------------------------------------------------- 1 | ## X API 脚本 2 | 3 | ## X OAuth 2.0 配置指南 4 | 5 | ### 1. 创建和配置应用 6 | 7 | #### 1.1 创建应用 8 | 1. 访问 [X开发者平台](https://developer.x.com/en/portal/dashboard) 9 | 2. 使用你的 X 账号登录 10 | 3. 点击'Sign up for Free Account'注册免费账户 11 | - 描述 Twitter 数据和 API 的所有使用案例(根据[开发者政策支持](https://developer.x.com/en/support/x-api/policy#faq-developer-use-case)去ds生成一个使用案例) 12 | 4. 填写项目基本信息: 13 | - 项目名称:自定义一个名称 14 | - 项目描述:简单描述项目用途 15 | 5. 在项目设置中进行用户身份验证设置 16 | - 应用程序权限选择:读写和直接留言 17 | - 应用程序类型选择:Web 应用程序、自动化应用程序或机器 18 | - 应用信息 19 | - 回调 URI/重定向 URL填写:https://x.com/home 20 | - 网站网址填写:https://x.com 21 | 6. 第五步填写完后跳转到OAuth 2.0页面,记下OAuth 2.0的 Client ID 和 Client Secret 22 | 23 | ### 2. 环境变量配置 24 | 25 | 在项目的 `.env` 文件中添加以下配置: 26 | 27 | ```bash 28 | # X OAuth 配置 29 | xClientId = '你的客户端ID' 30 | xClientSecret = '你的客户端密钥' 31 | xRedirectUri = '你的重定向URI' 32 | ``` 33 | 34 | ### 3. 注意事项 35 | 36 | 1. 免费的应用有很多限制(如果需要操作的号很多,那么可能需要别的x号申请多个应用程序) 37 | - 只能创建一个免费应用程序 38 | - 只有一个环境 39 | - 每月最多检索 100 篇帖子,发送 500 篇文章。详情:[X API V2 产品](https://developer.x.com/en/portal/products/free) 40 | 41 | 2. API 限制: 42 | - 注意遵守 X API 的速率限制 43 | - 建议实现请求限流机制 44 | - 关注API配额使用情况 45 | 46 | 3. 安全性: 47 | - 不要在代码中硬编码 client_secret 48 | - 保护好 .env 文件 49 | - 定期更新客户端密钥 50 | 51 | 4. 常见问题: 52 | - 如果无法获取 refresh_token: 53 | * 确保请求了 offline.access 权限 54 | * 验证重定向URI配置是否正确 55 | - API调用失败时: 56 | * 检查token是否过期 57 | * 验证权限范围是否足够 58 | * 确认API限流情况 59 | 60 | ### 4. 相关文档 61 | 62 | - [X API 文档](https://developer.x.com/docs) 63 | - [twitter-api-v2 文档](https://github.com/PLhery/node-twitter-api-v2/blob/master/doc/v2.md) 64 | 65 | ## 使用示例 66 | 67 | ```js 68 | import { XAuthenticator, XClient, XRpa } from './x.js'; 69 | 70 | // OAuth2.0授权示例 71 | const xAuth = await XAuthenticator.create({ 72 | browserType: 'chrome', // 浏览器类型:'chrome'或'bitbrowser' 73 | browserId: 1, // Chrome实例编号或BitBrowser浏览器ID 74 | socksProxyUrl: 'socks5://username:password@ip:port' // 代理地址 75 | }); 76 | await xAuth.authorizeAndSaveToken({ 77 | csvFile: './data/social/x.csv', 78 | matchField: 'xUsername', 79 | matchValue: 'xxx', 80 | targetField: 'xRefreshToken' 81 | }); 82 | 83 | // API操作示例(每次调用会产生新的刷新令牌,所以需要用新的刷新令牌替换掉文件里的旧的刷新令牌)。 84 | const xClient = await XClient.create({ 85 | refreshToken: 'x刷新令牌', 86 | socksProxyUrl: 'socks5://username:password@ip:port', 87 | csvFile: './data/social/x.csv', 88 | matchField: 'xUsername', 89 | matchValue: 'xxx', 90 | targetField: 'xRefreshToken' 91 | }); 92 | 93 | // 获取当前用户信息 94 | const userInfo = await xClient.getCurrentUserProfile(); 95 | console.log(userInfo); 96 | 97 | // 发送推文 98 | const tweetId = await xClient.tweet('Hello World!'); 99 | console.log(tweetId); 100 | 101 | // 关注用户 102 | await xClient.follow('elonmusk'); 103 | 104 | // RPA操作示例(需要指纹浏览器配合) 105 | const xRpa = await XRpa.create({ 106 | browserType: 'chrome', // 浏览器类型:'chrome'或'bitbrowser' 107 | browserId: 1 // Chrome实例编号或BitBrowser浏览器ID 108 | }); 109 | 110 | // 登录账号 111 | await xRpa.loginX('username', 'password', 'otpSecretKey'); 112 | 113 | // 切换界面语言为英文 114 | await xRpa.changeLanguage(); 115 | 116 | // 修改密码 117 | await xRpa.changePassword({ 118 | oldPassword: 'oldPassword', 119 | csvFile: './data/social/x.csv', 120 | matchField: 'xUsername', 121 | matchValue: 'xxx', 122 | targetField: 'xPassword' 123 | }); -------------------------------------------------------------------------------- /packages/utils-module/README.md: -------------------------------------------------------------------------------- 1 | # 工具库模块 2 | 3 | ## 创建钱包 `generateWallet.js` 4 | 5 | 创建的钱包文件存储在data目录下,敏感字段自动加密存储,文件名称(eth示例):`walletEth-2024-10-24_06-24-08.csv`。添加时间是为了防止已经存在同名文件,生成的新地址会追加到存在的文件中,将文件数据搞乱。实际使用中文件名称需要跟`filesConfig.json`中保持一致。 6 | 7 | ``` 8 | import { generateBtcWallet, generateEthWallet, generateSolWallet, generateSuiWallet } from './generateWallet.js'; 9 | 10 | // 批量生成各链地址(默认生成10个地址)字段(eth示例):indexId,ethAddress,enEthPrivateKey,enEthMnemonic 11 | generateBtcWallet(num); 12 | generateEthWallet(num); 13 | generateSolWallet(num); 14 | generateSuiWallet(num); 15 | ``` 16 | 17 | ## 格式化数据 `formatdata.js` 18 | 19 | 此文件配合`filesConfig.json`文件用来格式化数据,是将所有准备好的数据文件按照`indexId`组合到一起,方便使用。 20 | 21 | ``` 22 | import { myFormatData } from './formatdata.js'; 23 | 24 | myFormatData(1, 2) // 1 2 25 | myFormatData([1, 3]) // 1 2 3 26 | myFormatData(1, [2, 4], 6) // 1 2 3 4 6 27 | ``` 28 | 29 | 结果如下, 以myFormatData(1, 2)为例 30 | ``` 31 | [ 32 | { 33 | indexId: '1', 34 | btcAddress: '地址1', 35 | enBtcMnemonic: '加密后的助记词', 36 | enBtcPrivateKey: '加密后的私钥', 37 | ethAddress: '地址1', 38 | enEthMnemonic: '加密后的助记词', 39 | enEthPrivateKey: '加密后的私钥', 40 | solAddress: '地址1', 41 | enSolMnemonic: '加密后的助记词', 42 | enSolPrivateKey: '加密后的私钥', 43 | suiAddress: '地址1', 44 | enSuiPrivateKey: '加密后的私钥', 45 | binanceEthAddress: '地址1', 46 | okxEthAddress: '地址1', 47 | okxBtcAddress: '地址1', 48 | okxSolAddress: '地址1', 49 | okxStarknetAddress: '地址1', 50 | baseProxy: 'username:password@ip:port', 51 | socksProxyUrl: 'socks5://username:password@ip:port', 52 | httpProxyUrl: 'http://username:password@ip:port', 53 | httpsProxyUrl: 'https://username:password@ip:port', 54 | ...... 55 | }, 56 | { 57 | indexId: '2', 58 | indexId: '2', 59 | btcAddress: '地址2', 60 | enBtcMnemonic: '加密后的助记词', 61 | enBtcPrivateKey: '加密后的私钥', 62 | ethAddress: '地址2', 63 | enEthMnemonic: '加密后的助记词', 64 | enEthPrivateKey: '加密后的私钥', 65 | solAddress: '地址2', 66 | enSolMnemonic: '加密后的助记词', 67 | enSolPrivateKey: '加密后的私钥', 68 | suiAddress: '地址2', 69 | enSuiPrivateKey: '加密后的私钥', 70 | binanceEthAddress: '地址2', 71 | okxEthAddress: '地址2', 72 | okxBtcAddress: '地址2', 73 | okxSolAddress: '地址2', 74 | okxStarknetAddress: '地址2', 75 | baseProxy: 'username:password@ip:port', 76 | socksProxyUrl: 'socks5://username:password@ip:port', 77 | httpProxyUrl: 'http://username:password@ip:port', 78 | httpsProxyUrl: 'https://username:password@ip:port', 79 | ...... 80 | }, 81 | ...... 82 | ] 83 | ``` 84 | 85 | ## 生成一次性密码 `otp.js` 86 | 87 | ```js 88 | import { getOTP } from './otp.js'; 89 | 90 | // 参数:otp私钥, 剩余过期时间(秒) 91 | await getOTP(otpSecretKey, minRemainingTime); 92 | ``` 93 | 94 | ## 获取路径 `path.js` 95 | 96 | 路径问题挺烦的,有时候改变项目目录,路径就变了。 97 | 98 | ```js 99 | import { getPathFromRoot, getPathFromCurrentDir, pathExists, makeSureDirExists } from './path.js'; 100 | 101 | // 获取项目根目录路径 102 | const rootPath = getPathFromRoot(); 103 | 104 | // 获取当前文件目录路径 105 | const currentDirPath = getPathFromCurrentDir(import.meta.url); 106 | 107 | // 获取相对于当前文件目录的路径 108 | const currentDirPath = getPathFromCurrentDir(import.meta.url, 'data'); 109 | 110 | ``` 111 | 112 | ## 检查地址中奖情况 `check.js` 113 | 114 | ```js 115 | import { check } from './check.js'; 116 | 117 | // 参数:中奖文件路径(接受json、txt、xlsx格式), 我们的地址CSV文件路径, 我们CSV文件中的地址列名 118 | await check({winFilePath, ourCsvPath, columnName}); 119 | ``` 120 | 121 | ## 过谷歌验证码 `captcha.js` 122 | 123 | 目前使用了三个服务商 124 | - yescaptcha:https://yescaptcha.com 125 | - nocaptcha:https://www.nocaptcha.io 126 | - capSolver:https://www.capsolver.com 127 | 128 | ```js 129 | import { captchaManager } from './captcha.js'; 130 | 131 | await captchaManager.verifyWebsite({ 132 | captchaService: 'yesCaptcha', // captcha服务商,yesCaptcha|noCatpcha|capSolver等 133 | captchaType: 'reCaptchaV2', // 任务类型,reCaptchaV2|reCaptchaV3|hCaptcha等 134 | taskVariant: 'standard', // 类型变体,standard|advanced|k1|m1|m1s7|m1s9等 135 | websiteURL: '', 136 | websiteKey: '' 137 | }); 138 | 139 | await captchaManager.verifyWebsite({ 140 | captchaService: 'noCaptcha', // captcha服务商,yesCaptcha|noCatpcha|capSolver等 141 | captchaType: 'reCaptcha', // 任务类型,reCaptcha|hCaptcha等 142 | taskVariant: 'universal', // 类型变体,universal等 143 | sitekey: '', 144 | referer: '', 145 | title: '', 146 | size: 'normal' 147 | }); 148 | 149 | await captchaManager.verifyWebsite({ 150 | captchaService: 'capSolver', // captcha服务商,yesCaptcha|noCatpcha|capSolver等 151 | captchaType: 'geeTestV4', // 任务类型,reCaptchaV2|reCaptchaV3|hCaptcha|geeTestV3|geeTestV4等 152 | taskVariant: 'standard', // 类型变体,standard|advanced|k1|m1|m1s7|m1s9等 153 | websiteURL: '', 154 | captchaId: '' 155 | }); 156 | ``` 157 | 158 | 如何获取captcha类型以及需要的各个参数请查看文档 159 | - yescatpcha开发文档:https://yescaptcha.atlassian.net/wiki/spaces/YESCAPTCHA/overview 160 | - nocaptcha开发文档:https://chrisyp.github.io/ 161 | - capsolver开发文档:https://docs.capsolver.com/zh/ 162 | 163 | ## 重试机制 `retry.js` 164 | 165 | 通用的异步操作重试机制,支持指数退避延迟和自定义重试次数。 166 | 167 | ```js 168 | import { withRetry } from './retry.js'; 169 | 170 | const result = await withRetry( 171 | async () => { 172 | // 异步操作 173 | // 处理业务错误,抛出异常会触发重试。 174 | throw new Error('业务错误信息'); 175 | 176 | // 成功则返回数据,返回给withRetry函数 177 | return data; 178 | }, 179 | { 180 | maxRetries: 5, // 最大重试次数,默认3次 181 | delay: 2000, // 重试延迟(ms),默认1000ms 182 | taskName: '连接浏览器', // 任务名称,用于日志 183 | logContext: { // 日志上下文信息 184 | // 根据自己的业务需求写 185 | number: 1, 186 | address: '0x...' 187 | } 188 | } 189 | ); 190 | // 处理返回值 191 | return result; 192 | 193 | // 写法2 194 | return withRetry( 195 | // 内容如上 196 | ); 197 | 198 | // 日志输出示例: 199 | 200 | // 网络错误 201 | // 连接浏览器失败 [chromeNumber 1] [address 0x...] [重试 准备第n次重试] [原因 连接被拒绝,目标服务未启动或端口未开启] 202 | // 连接浏览器失败 [chromeNumber 1] [address 0x...] [重试 准备第n次重试] [原因 连接超时] 203 | 204 | // 业务错误 205 | // 连接浏览器失败 [chromeNumber 1] [address 0x...] [重试 准备第n次重试] [原因 业务错误信息] 206 | 207 | // 达到最大失败次数 208 | // 连接浏览器失败 [chromeNumber 1] [address 0x...] [达到最大重试次数 n] [原因 ...] 209 | ``` 210 | 211 | ## 工具库函数示例 212 | 213 | 只列出部分函数,完整函数请看`utils.js`文件 214 | 215 | ``` 216 | import { getCsvData, getCsvDataByColumnName } from './utils.js'; 217 | 218 | // 从指定的 CSV 文件中读取数据并返回解析后的结果 219 | await getCsvData('./data/wallet/walletEth.csv'); 220 | 221 | // 从指定的 CSV 文件中读取数据,并将指定列的数据转存到临时文件。 222 | // 参数;csvFile, columnName, tempFile='./data/temp.csv' 223 | await getCsvDataByColumnName('./data/wallet/walletEth.csv', 'address'); 224 | ``` -------------------------------------------------------------------------------- /packages/utils-module/check.js: -------------------------------------------------------------------------------- 1 | import fsPromises from 'node:fs/promises'; 2 | import path from 'node:path'; 3 | import xlsx from 'xlsx'; 4 | import { getCsvDataByColumnName } from './utils.js'; 5 | import { notificationManager } from '../notification-module/notification.js'; 6 | 7 | /** 8 | * 检查地址中奖情况 9 | * @param {Object} params - 参数对象 10 | * @param {string} params.winFilePath - 中奖文件路径,支持以下格式: 11 | * 1. JSON格式: {"address1": 3000, "address2": 2000} 12 | * 2. TXT格式(只处理前两列数据,剩下的列忽略。第一列地址,第二列数量,数量列可没有,支持逗号、竖线、空格分隔) : address,amount 13 | * 3. Excel格式(只处理前两列数据,剩下的列忽略。第一列地址,第二列数量,数量列可没有) : address,amount 14 | * @param {string} params.ourCsvPath - 我们的地址CSV文件路径 15 | * @param {string} params.columnName - 我们CSV文件中的地址列名 16 | * @returns {Array} 返回中奖结果数组,每个对象包含地址和金额(如果有) 17 | */ 18 | export async function check({ 19 | winFilePath, 20 | ourCsvPath, 21 | columnName 22 | }) { 23 | try { 24 | const fileType = path.extname(winFilePath).toLowerCase(); 25 | let winningData = {}; 26 | 27 | switch (fileType) { 28 | case '.json': 29 | const jsonData = await fsPromises.readFile(winFilePath, 'utf-8'); 30 | const jsonParsed = JSON.parse(jsonData); 31 | 32 | // 检查是否为对象格式 33 | if (typeof jsonParsed !== 'object' || Array.isArray(jsonParsed)) { 34 | console.error('目前只能处理{"address1": amount1, "address2": amount2, ...}格式'); 35 | return; 36 | } 37 | 38 | // 检查是否为空 39 | if (Object.keys(jsonParsed).length === 0) { 40 | console.error('文件为空'); 41 | return; 42 | } 43 | 44 | // 直接使用解析后的对象 45 | winningData = jsonParsed; 46 | break; 47 | 48 | case '.txt': 49 | const txtData = await fsPromises.readFile(winFilePath, 'utf-8'); 50 | const lines = txtData.split('\n').filter(Boolean); 51 | if (lines.length === 0) { 52 | console.error('文件为空'); 53 | return; 54 | } 55 | 56 | // 检查第一行格式 57 | const firstLine = lines[0]; 58 | const separator = firstLine.includes(',') ? ',' : 59 | firstLine.includes('|') ? '|' : 60 | /\s+/; 61 | const fields = firstLine.trim().split(separator); 62 | 63 | // 检查字段数量并给出提示 64 | if (fields.length === 1) { 65 | console.log('提示:中奖文件只包含地址字段'); 66 | } else if (fields.length === 2) { 67 | console.log('提示:默认中奖文件第一列为地址,第二列为金额'); 68 | } else if (fields.length > 2) { 69 | console.log('提示:中奖文件包含多列数据,将只处理前两列(第一列为地址,第二列为金额)'); 70 | } 71 | 72 | // 处理数据行 73 | for (let i = 1; i < lines.length; i++) { 74 | const parts = lines[i].trim().split(separator); 75 | const address = parts[0]; // 始终取第一列作为地址 76 | if (address) { 77 | if (parts.length >= 2) { 78 | const amount = parseFloat(parts[1]) || 0; // 只有当有第二列时才处理金额 79 | winningData[address] = amount; 80 | } else { 81 | winningData[address] = undefined; // 只有地址时不设置金额 82 | } 83 | } 84 | } 85 | break; 86 | 87 | case '.xlsx': 88 | case '.xls': 89 | const workbook = xlsx.readFile(winFilePath); 90 | const worksheet = workbook.Sheets[workbook.SheetNames[0]]; 91 | const excelData = xlsx.utils.sheet_to_json(worksheet, { header: 1 }); // 使用数组格式 92 | 93 | if (excelData.length === 0) { 94 | console.error('文件为空'); 95 | return; 96 | } 97 | 98 | // 检查字段数量并给出提示 99 | const columnCount = excelData[0].length; 100 | if (columnCount === 1) { 101 | console.log('提示:中奖文件只包含地址字段'); 102 | } else if (columnCount === 2) { 103 | console.log('提示:默认中奖文件第一列为地址,第二列为金额'); 104 | } else if (columnCount > 2) { 105 | console.log('提示:中奖文件包含多列数据,将只处理前两列(第一列为地址,第二列为金额)'); 106 | } 107 | 108 | // 处理数据行 109 | for (let i = 1; i < excelData.length; i++) { 110 | const row = excelData[i]; 111 | const address = row[0]; // 始终取第一列作为地址 112 | if (address) { 113 | if (row.length >= 2) { 114 | const amount = parseFloat(row[1]) || 0; // 只有当有第二列时才处理金额 115 | winningData[address] = amount; 116 | } else { 117 | winningData[address] = undefined; // 只有地址时不设置金额 118 | } 119 | } 120 | } 121 | break; 122 | 123 | default: 124 | console.error(`不支持的文件类型: ${fileType},目前支持: .json, .txt, .xlsx, .xls`); 125 | return; 126 | } 127 | 128 | // 将中奖数据转换为小写格式 129 | const winnerSet = new Set(); // 用于快速判断是否中奖 130 | const winnerAmounts = new Map(); // 用于存储中奖金额 131 | 132 | for (const [address, amount] of Object.entries(winningData)) { 133 | const lowerAddress = address.toLowerCase(); 134 | winnerSet.add(lowerAddress); 135 | winnerAmounts.set(lowerAddress, amount); 136 | } 137 | 138 | // 获取并检查我们的地址 139 | const ourAddresses = await getCsvDataByColumnName({csvFile:ourCsvPath, columnName}); 140 | if (!ourAddresses || !Array.isArray(ourAddresses)) { 141 | throw new Error('读取我们的地址数据失败'); 142 | } 143 | 144 | // 统计结果 145 | let totalWinners = 0; 146 | let totalAmount = 0; 147 | const results = []; 148 | const allResults = []; 149 | const hasAmounts = [...winnerAmounts.values()].some(amount => amount !== undefined); 150 | 151 | // 检查每个地址 152 | for (const address of ourAddresses) { 153 | const lowerAddress = address.toLowerCase(); 154 | const isWinner = winnerSet.has(lowerAddress); 155 | const amount = isWinner ? winnerAmounts.get(lowerAddress) : undefined; 156 | 157 | // 保存所有结果用于打印 158 | allResults.push({ 159 | address, // 保持原始大小写显示 160 | won: isWinner, 161 | ...(hasAmounts && amount !== undefined && { amount }) 162 | }); 163 | 164 | // 只保存中奖结果用于返回 165 | if (isWinner) { 166 | results.push({ 167 | address, // 保持原始大小写显示 168 | ...(hasAmounts && amount !== undefined && { amount }) 169 | }); 170 | totalWinners++; 171 | if (hasAmounts && amount !== undefined) { 172 | totalAmount += amount; 173 | } 174 | } 175 | } 176 | 177 | // 打印统计信息 178 | notificationManager.info(`中奖统计 [总地址数 ${ourAddresses.length}] [中奖数 ${totalWinners}] [中奖率 ${((totalWinners / ourAddresses.length) * 100).toFixed(2)}%]`); 179 | if (hasAmounts) { 180 | notificationManager.info(`[总中奖金额 ${totalAmount}]`); 181 | } 182 | 183 | // 打印详细信息 184 | notificationManager.info(`\n=== 详细地址情况 ===`); 185 | allResults.forEach((result, index) => { 186 | notificationManager.info(`[序号 ${index + 1}] [地址 ${result.address}]`); 187 | if (result.won) { 188 | notificationManager.success(`[状态 🎉 中奖]${hasAmounts && result.amount !== undefined ? ` [金额 ${result.amount}]` : ''}`); 189 | } else { 190 | notificationManager.error(`[状态 ❌ 未中奖]`); 191 | } 192 | }); 193 | 194 | // 只返回中奖结果 195 | return results; 196 | 197 | } catch (error) { 198 | notificationManager.error(`检查中奖失败 [原因 ${error.message}]`); 199 | return; 200 | } 201 | } -------------------------------------------------------------------------------- /packages/utils-module/formatdata.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { getPathFromRoot } from './path.js'; 4 | import { getCsvData, getExcelData, parseInstanceNumbers, formatNumber } from './utils.js'; 5 | 6 | // 整合多个 CSV 文件为一个 JSON 对象 7 | export async function myFormatData(...inputs) { 8 | const instanceNumbers = parseInstanceNumbers(...inputs); 9 | if (instanceNumbers.length === 0) { 10 | console.log('至少输入一个账号'); 11 | return; 12 | } 13 | 14 | const allData = []; 15 | 16 | // 读取配置文件 17 | const config = JSON.parse(fs.readFileSync(getPathFromRoot('filesConfig.json'), 'utf-8')); 18 | const dataFiles = config.dataFiles; 19 | 20 | for (const relativePath of dataFiles) { 21 | // 将相对路径转换为绝对路径 22 | const filePath = getPathFromRoot(relativePath); 23 | let records; 24 | try { 25 | // 读取 CSV 或 Excel 文件 26 | if (filePath.endsWith('.csv')) { 27 | records = await getCsvData(filePath); // 读取 CSV 文件 28 | } else if (filePath.endsWith('.xlsx')) { 29 | records = await getExcelData(filePath); // 读取 Excel 文件 30 | } else { 31 | console.log(`不支持的文件类型: ${filePath}`); 32 | continue; // 跳过不支持的文件类型 33 | } 34 | 35 | records.forEach(record => { 36 | const id = parseInt(record.indexId); // 使用每个文件中的 indexId 字段并转换为整数 37 | // 只处理在 instanceNumbers 数组中的 ID 38 | if (instanceNumbers.includes(id)) { 39 | if (filePath.includes('ip.csv')) { 40 | record.baseProxy = `${record.proxyUsername}:${record.proxyPassword}@${record.proxyIp}:${record.proxyPort}`; 41 | record.socksProxyUrl = `socks5://${record.baseProxy}`; 42 | record.httpProxyUrl = `http://${record.baseProxy}`; 43 | record.httpsProxyUrl = `https://${record.baseProxy}`; 44 | } 45 | 46 | // 查找是否已经存在该 indexId 的记录 47 | const existingRecord = allData.find(item => item.indexId === id.toString()); 48 | if (existingRecord) { 49 | // 如果存在,合并数据 50 | Object.assign(existingRecord, record); // 合并当前记录到已存在的记录中 51 | } else { 52 | // 如果不存在,添加新记录,并将 indexId 放在第一个字段 53 | const newRecord = { indexId: id.toString(), ...record }; // 创建新记录 54 | allData.push(newRecord); // 将当前记录添加到 allData 数组中 55 | } 56 | } 57 | }); 58 | } catch (error) { 59 | console.error(`处理文件 ${filePath} 时出错:`, error); 60 | } 61 | } 62 | 63 | // 添加指纹文件内容 64 | for (const id of instanceNumbers) { 65 | const chromeDir = `Chrome${formatNumber(id, 3)}`; 66 | const basePath = config.fingerprintConfig.basePath.replace('$HOME', process.env.HOME); 67 | const fingerprintPath = path.join(basePath, chromeDir, config.fingerprintConfig.fileName); 68 | 69 | const existingRecord = allData.find(item => item.indexId === id.toString()); 70 | if (existingRecord) { 71 | try { 72 | if (fs.existsSync(fingerprintPath)) { 73 | // 读取并解析指纹文件内容 74 | const fingerprintContent = JSON.parse(fs.readFileSync(fingerprintPath, 'utf8')); 75 | 76 | // 提取关键信息 77 | existingRecord.fingerprint = { 78 | // 基本浏览器信息 79 | userAgent: fingerprintContent.fingerprint.navigator.userAgent, 80 | // 请求头信息 81 | headers: fingerprintContent.headers, 82 | // 屏幕信息(可能有用) 83 | screen: fingerprintContent.fingerprint.screen, 84 | // WebGL信息(反爬可能会用到) 85 | videoCard: fingerprintContent.fingerprint.videoCard, 86 | // 音频编解码器支持情况 87 | audioCodecs: fingerprintContent.fingerprint.audioCodecs, 88 | // 字体列表 89 | fonts: fingerprintContent.fingerprint.fonts 90 | }; 91 | } else { 92 | console.warn(`警告: 指纹文件不存在 ${fingerprintPath}`); 93 | } 94 | } catch (error) { 95 | console.error(`读取指纹文件失败 ${fingerprintPath}:`, error); 96 | } 97 | } 98 | } 99 | // console.log(allData); 100 | return allData; 101 | } -------------------------------------------------------------------------------- /packages/utils-module/generateWallet.js: -------------------------------------------------------------------------------- 1 | import fsPromises from 'fs/promises'; // 使用 promises API,并将导入名称更改为 fsPromises 2 | import bs58 from 'bs58'; 3 | 4 | import * as bip39 from 'bip39'; 5 | import * as bip32 from 'bip32'; 6 | import * as ecc from 'tiny-secp256k1'; 7 | import { ECPairFactory } from 'ecpair'; 8 | import * as bitcoin from 'bitcoinjs-lib'; 9 | 10 | import { ethers } from 'ethers-v6'; 11 | 12 | import solanaWeb3 from '@solana/web3.js'; 13 | 14 | import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; 15 | 16 | import { getCurrentTime, generateRandomString } from './utils.js'; 17 | import { enCryptText } from '../crypt-module/crypt.js'; 18 | 19 | /** 20 | * 创建指定数量的以太坊钱包,并将其信息加密存储到 CSV 文件中。 21 | * @param {number} num - 要创建的钱包数量,默认为 10。 22 | */ 23 | export async function generateEthWallet(num = 10) { 24 | const currentTime = getCurrentTime(); 25 | const walletfile = `./data/wallet/walletEth-${currentTime}.csv`; // 生成文件名 26 | 27 | // 判断文件是否存在 28 | try { 29 | await fsPromises.access(walletfile); 30 | } catch { 31 | // 文件不存在则创建文件并写入标题行 32 | const header = 'indexId,ethAddress,enEthPrivateKey,enEthMnemonic\n'; 33 | await fsPromises.writeFile(walletfile, header); 34 | } 35 | 36 | const file = await fsPromises.open(walletfile, 'a'); 37 | 38 | for (let i = 1; i <= num; i++) { 39 | const wallet = ethers.Wallet.createRandom(); 40 | const enPrivateKey = await enCryptText(wallet.privateKey); 41 | const enMnemonic = await enCryptText(wallet.mnemonic.phrase); 42 | const rowData = `${i},${wallet.address},${enPrivateKey},${enMnemonic}\n`; 43 | 44 | // 文件存在则追加 45 | await file.appendFile(rowData); 46 | } 47 | 48 | await file.close(); 49 | console.log(`已将 ${num} 个钱包存储到 ${walletfile}`); 50 | } 51 | 52 | /** 53 | * 创建指定数量的比特币钱包,并将其信息加密存储到 CSV 文件中。 54 | * @param {number} num - 要创建的钱包数量,默认为 10。 55 | */ 56 | export async function generateBtcWallet(num = 10) { 57 | //将 tiny-secp256k1 库初始化为比特币库(bitcoinjs-lib)所使用的椭圆曲线加密库。即 bitcoinjs-lib 库将能够利用 tiny-secp256k1 进行加密操作。 58 | bitcoin.initEccLib(ecc); 59 | 60 | // Taproot 地址需要的公钥是 32 字节的哈希值(即 x 值),而不是 33 字节的压缩公钥(需要去掉压缩公钥的前缀字节(如0x02)) 61 | const convertToXOnly = (pubKey) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); 62 | 63 | const currentTime = getCurrentTime(); 64 | const walletfile = `./data/wallet/walletBtc-${currentTime}.csv`; // 生成文件名 65 | 66 | // 判断文件是否存在 67 | try { 68 | await fsPromises.access(walletfile); 69 | } catch { 70 | // 文件不存在则创建文件并写入标题行 71 | const header = 'indexId,btcAddress,enBtcPrivateKey,enBtcMnemonic\n'; 72 | await fsPromises.writeFile(walletfile, header); 73 | } 74 | 75 | const file = await fsPromises.open(walletfile, 'a'); 76 | 77 | for (let i = 1; i <= num; i++) { 78 | const network = bitcoin.networks.bitcoin; // 指定比特币网络 79 | // 生成助记词 80 | const mnemonic = bip39.generateMnemonic(); // 生成 12 个单词的助记词 81 | const seed = bip39.mnemonicToSeedSync(mnemonic); // 将助记词转换为种子 82 | const root = bip32.BIP32Factory(ecc).fromSeed(seed); // 通过种子生成根秘钥 83 | const child = root.derivePath("m/86'/0'/0'/0/0"); // 根据 BIP86 生成密钥对 84 | const privateKeyBuffer = Buffer.from(child.privateKey); // 将 child.privateKey 从 Uint8Array格式 转换为 Buffer 85 | const keyPair = ECPairFactory(ecc).fromPrivateKey(privateKeyBuffer, { network }); // 通过私钥创建一个 keyPair 密钥对 86 | const { address } = bitcoin.payments.p2tr({ internalPubkey: convertToXOnly(keyPair.publicKey), network }); 87 | const privateKey = keyPair.toWIF(); // 将privateKey转换为WIF格式 88 | // console.log(address) 89 | // console.log(mnemonic) 90 | // console.log(privateKey) 91 | const enPrivateKey = await enCryptText(privateKey); 92 | const enMnemonic = await enCryptText(mnemonic); 93 | const rowData = `${i},${address},${enPrivateKey},${enMnemonic}\n`; 94 | 95 | // 文件存在则追加 96 | await file.appendFile(rowData); 97 | } 98 | 99 | await file.close(); 100 | console.log(`已将 ${num} 个钱包存储到 ${walletfile}`); 101 | } 102 | 103 | /** 104 | * 创建指定数量的 Solana 钱包,并将其信息加密存储到 CSV 文件中。 105 | * @param {number} num - 要创建的钱包数量,默认为 10。 106 | */ 107 | export async function generateSolWallet(num = 10) { 108 | const currentTime = getCurrentTime(); 109 | const walletfile = `./data/wallet/walletSol-${currentTime}.csv`; // 生成文件名 110 | 111 | // 判断文件是否存在 112 | try { 113 | await fsPromises.access(walletfile); 114 | } catch { 115 | // 文件不存在则创建文件并写入标题行 116 | const header = 'indexId,solAddress,enSolPrivateKey\n'; 117 | await fsPromises.writeFile(walletfile, header); 118 | } 119 | 120 | const file = await fsPromises.open(walletfile, 'a'); 121 | 122 | for (let i = 1; i <= num; i++) { 123 | const keyPair = solanaWeb3.Keypair.generate(); 124 | const address = keyPair.publicKey.toString(); 125 | const secretKey = keyPair.secretKey; 126 | const base58EncodedKey = bs58.encode(secretKey); 127 | const enPrivateKey = await enCryptText(base58EncodedKey); 128 | 129 | const rowData = `${i},${address},${enPrivateKey}\n`; 130 | // 文件存在则追加 131 | await file.appendFile(rowData); 132 | } 133 | 134 | await file.close(); 135 | console.log(`已将 ${num} 个钱包存储到 ${walletfile}`); 136 | } 137 | 138 | /** 139 | * 创建指定数量的 Sui 钱包,并将其信息加密存储到 CSV 文件中。 140 | * @param {number} num - 要创建的钱包数量,默认为 10。 141 | */ 142 | export async function generateSuiWallet(num = 10) { 143 | const currentTime = getCurrentTime(); 144 | const walletfile = `./data/wallet/walletSui-${currentTime}.csv`; // 生成文件名 145 | 146 | // 判断文件是否存在 147 | try { 148 | await fsPromises.access(walletfile); 149 | } catch { 150 | // 文件不存在则创建文件并写入标题行 151 | const header = 'indexId,suiAddress,enSuiPrivateKey\n'; 152 | await fsPromises.writeFile(walletfile, header); 153 | } 154 | 155 | const file = await fsPromises.open(walletfile, 'a'); 156 | 157 | for (let i = 1; i <= num; i++) { 158 | const keypair = new Ed25519Keypair(); 159 | const address = keypair.getPublicKey().toSuiAddress(); 160 | const enPrivateKey = await enCryptText(keypair.getSecretKey()); 161 | const rowData = `${i},${address},${enPrivateKey}\n`; 162 | 163 | // 文件存在则追加 164 | await file.appendFile(rowData); 165 | } 166 | 167 | await file.close(); 168 | console.log(`已将 ${num} 个钱包存储到 ${walletfile}`); 169 | } 170 | 171 | /** 172 | * 生成指定数量的随机密码,并将其加密后存储到 CSV 文件中。 173 | * @param {number} num - 要生成的密码数量,默认为 10。 174 | */ 175 | export async function generatePassword(num = 10) { 176 | const currentTime = getCurrentTime(); 177 | const walletfile = `./data/wallet/walletPassword-${currentTime}.csv`; // 生成文件名 178 | 179 | // 判断文件是否存在 180 | try { 181 | await fsPromises.access(walletfile); 182 | } catch { 183 | // 文件不存在则创建文件并写入标题行 184 | const header = 'indexId,enPassword\n'; 185 | await fsPromises.writeFile(walletfile, header); 186 | } 187 | 188 | const file = await fsPromises.open(walletfile, 'a'); 189 | 190 | for (let i = 1; i <= num; i++) { 191 | const randomPassword = generateRandomString(18); 192 | // console.log(randomPassword); 193 | const enPassword = await enCryptText(randomPassword); 194 | const rowData = `${i},${enPassword}\n`; 195 | 196 | // 文件存在则追加 197 | await file.appendFile(rowData); 198 | } 199 | 200 | await file.close(); 201 | console.log(`已将 ${num} 个钱包存储到 ${walletfile}`); 202 | } -------------------------------------------------------------------------------- /packages/utils-module/otp.js: -------------------------------------------------------------------------------- 1 | import { authenticator } from 'otplib'; 2 | 3 | /** 4 | * 生成一次性密码(OTP),确保返回的OTP至少还有minRemainingTime秒的有效期,避免时间过短来不及使用。 5 | * @param {string} otpSecretKey - 用于生成OTP的密钥。 6 | * @param {number} minRemainingTime - 最小剩余时间(秒),如果密码的剩余有效时间小于等于此时间,则重新生成密码。 7 | * @returns {Promise} 返回生成的一次性密码。 8 | */ 9 | const MAX_RETRIES = 3; 10 | 11 | export async function getOTP(otpSecretKey, minRemainingTime = 3) { 12 | 13 | let retryCount = 0; 14 | 15 | while (retryCount < MAX_RETRIES) { 16 | try { 17 | const otp = await authenticator.generate(otpSecretKey); 18 | const remainingTimeInSeconds = authenticator.timeRemaining(); 19 | 20 | if (remainingTimeInSeconds > minRemainingTime) { 21 | return otp; 22 | } 23 | 24 | console.log(`OTP剩余时间不足 ${minRemainingTime} 秒,等待 ${remainingTimeInSeconds + 1} 秒后重试...`); 25 | await new Promise(resolve => setTimeout(resolve, (remainingTimeInSeconds + 1) * 1000)); 26 | retryCount++; 27 | 28 | } catch (error) { 29 | console.error('生成OTP时发生错误:', error); 30 | throw error; 31 | } 32 | } 33 | 34 | throw new Error(`无法生成有效的OTP, 已重试 ${MAX_RETRIES} 次`); 35 | } -------------------------------------------------------------------------------- /packages/utils-module/path.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url'; 2 | import { dirname, join } from 'path'; 3 | import fsp from 'fs/promises'; 4 | 5 | // 获取项目根目录路径 6 | function getRootDir() { 7 | const __filename = fileURLToPath(import.meta.url); 8 | return dirname(dirname(dirname(__filename))); // 从当前文件往上3层到根目录 9 | } 10 | 11 | // 获取相对于项目根目录的路径 12 | export function getPathFromRoot(...paths) { 13 | return join(getRootDir(), ...paths); 14 | } 15 | 16 | // 获取相对于当前文件目录的路径 importMetaUrl 是当前文件的import.meta.url 17 | export function getPathFromCurrentDir(importMetaUrl, ...relativePaths) { 18 | const currentDir = dirname(fileURLToPath(importMetaUrl)); 19 | return relativePaths.length ? join(currentDir, ...relativePaths) : currentDir; 20 | } 21 | 22 | // 检查路径是否存在 23 | export async function pathExists(path) { 24 | try { 25 | await fsp.access(path); 26 | return true; 27 | } catch { 28 | return false; 29 | } 30 | } 31 | 32 | // 确保目录存在 33 | export async function makeSureDirExists(filePath) { 34 | try { 35 | const dir = dirname(filePath); 36 | await fsp.mkdir(dir, { recursive: true }); 37 | } catch (error) { 38 | console.error('创建目录失败:', error); 39 | throw error; 40 | } 41 | } -------------------------------------------------------------------------------- /packages/utils-module/retry.js: -------------------------------------------------------------------------------- 1 | import { notificationManager } from "../notification-module/notification.js"; 2 | 3 | /** 4 | * HTTP状态码错误信息映射 5 | */ 6 | const HTTP_ERROR_MESSAGES = { 7 | // 客户端错误 4xx 8 | 400: '请求参数错误,请检查请求数据', 9 | 401: '未授权或登录已过期,请重新登录', 10 | 402: '需要付费才能访问', 11 | 403: '无权访问该资源,请检查权限', 12 | 404: '请求的资源不存在,请检查URL', 13 | 405: '不允许使用该请求方法', 14 | 406: '请求的格式不被支持', 15 | 407: '需要代理认证', 16 | 408: '请求超时,请检查网络状况', 17 | 409: '资源冲突,可能已被修改', 18 | 410: '请求的资源已永久删除', 19 | 411: '需要指定Content-Length', 20 | 412: '前提条件验证失败', 21 | 413: '请求内容过大', 22 | 414: 'URL过长', 23 | 415: '不支持的媒体类型', 24 | 416: '请求范围不合法', 25 | 417: '预期验证失败', 26 | 429: '请求频率过高,请稍后重试', 27 | 431: '请求头字段过大', 28 | 451: '因法律原因无法访问', 29 | 30 | // 服务器错误 5xx 31 | 500: '服务器内部错误,请稍后重试', 32 | 501: '服务器不支持该功能', 33 | 502: '网关错误,请检查服务是否可用', 34 | 503: '服务暂时不可用,可能在维护或过载', 35 | 504: '网关超时,请稍后重试', 36 | 505: '不支持的HTTP版本', 37 | 506: '服务器配置错误', 38 | 507: '服务器存储空间不足', 39 | 508: '检测到循环引用', 40 | 510: '需要进一步扩展协议', 41 | 511: '需要网络认证' 42 | }; 43 | 44 | /** 45 | * 网络错误映射表 46 | */ 47 | const NETWORK_ERROR_MAP = { 48 | // 连接错误 49 | 'ECONNREFUSED': '连接被拒绝,目标服务未启动或端口未开启', 50 | 'ECONNRESET': '连接被重置,服务器可能已关闭连接', 51 | 'ECONNABORTED': '连接已中止,可能是客户端取消了请求', 52 | // 超时错误 53 | 'ETIMEDOUT': '连接超时,服务器响应时间过长', 54 | 'network timeout': '网络超时,请检查网络连接', 55 | 'timeout': '请求超时,服务器响应时间过长', 56 | // 网络错误 57 | 'fetch failed': '网络请求失败,请检查网络连接', 58 | 'ERR_NETWORK': '网络错误,请检查网络连接是否正常', 59 | 'ENOTFOUND': 'DNS解析失败,域名无法解析', 60 | 'CERT_HAS_EXPIRED': 'SSL证书已过期,请检查服务器证书', 61 | 'EPROTO': '协议错误,可能是SSL/TLS握手失败', 62 | // 其他错误 63 | 'EACCES': '访问被拒绝,没有权限访问资源', 64 | 'EADDRINUSE': '地址已被使用,端口可能被占用', 65 | 'ERR_CANCELED': '请求已被取消', 66 | 'status code': '请求失败,状态码异常' // 用于捕获一些通用的状态码错误 67 | }; 68 | 69 | /** 70 | * 获取HTTP错误的友好描述 71 | * @param {number} status - HTTP状态码 72 | * @param {string} statusText - 状态描述 73 | * @returns {string} 友好的错误描述 74 | */ 75 | function getHttpErrorMessage(status, statusText) { 76 | return HTTP_ERROR_MESSAGES[status] 77 | ? `${HTTP_ERROR_MESSAGES[status]} (HTTP ${status})` 78 | : `HTTP ${status}: ${statusText || '未知错误'}`; 79 | } 80 | 81 | /** 82 | * 获取网络错误的友好描述 83 | * @param {Error} error - 错误对象 84 | * @returns {string} 友好的错误描述 85 | */ 86 | function getNetworkErrorMessage(error) { 87 | // 处理 axios 的错误响应 88 | if (error.response && error.response.status) { 89 | const { status, statusText } = error.response; 90 | return getHttpErrorMessage(status, statusText); 91 | } 92 | 93 | // 处理请求超时 94 | if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) { 95 | return '请求超时,服务器响应时间过长'; 96 | } 97 | 98 | // 处理网络错误 99 | if (error.code) { 100 | const message = NETWORK_ERROR_MAP[error.code]; 101 | if (message) return message; 102 | } 103 | 104 | // 检查错误消息中的关键字 105 | const errorMessage = error.message || ''; 106 | for (const [key, message] of Object.entries(NETWORK_ERROR_MAP)) { 107 | if (errorMessage.toLowerCase().includes(key.toLowerCase())) { 108 | return message; 109 | } 110 | } 111 | 112 | // 处理状态码错误(有些库会直接在错误消息中包含状态码) 113 | const statusCodeMatch = errorMessage.match(/status code (\d+)/i); 114 | if (statusCodeMatch && statusCodeMatch[1]) { 115 | const status = parseInt(statusCodeMatch[1]); 116 | if (HTTP_ERROR_MESSAGES[status]) { 117 | return getHttpErrorMessage(status, ''); 118 | } 119 | } 120 | 121 | // 默认返回原始错误信息 122 | return errorMessage; 123 | } 124 | 125 | /** 126 | * 通用重试机制 127 | * @param {Function} task - 要执行的任务 128 | * @param {Object} [options] - 重试选项 129 | * @param {number} [options.maxRetries=3] - 最大重试次数 130 | * @param {number} [options.delay=1000] - 重试延迟(ms) 131 | * @param {string} [options.message='未命名任务'] - 任务消息 132 | * @param {Object} [options.context={}] - 上下文信息 133 | * @returns {Promise<*>} 任务结果 134 | * 135 | * @example 136 | * // fetch 用法 137 | * await withRetry( 138 | * () => fetch('http://example.com'), 139 | * { message: '请求示例' } 140 | * ); 141 | * 142 | * // axios 用法 143 | * await withRetry( 144 | * () => axios.get('http://example.com'), 145 | * { message: '请求示例' } 146 | * ); 147 | */ 148 | export async function withRetry(task, options = {}) { 149 | const { 150 | maxRetries = 3, 151 | delay = 1000, 152 | message = '未命名任务', 153 | context = {} 154 | } = options; 155 | 156 | for (let attempt = 1; attempt <= maxRetries; attempt++) { 157 | try { 158 | const response = await task(); 159 | 160 | // 如果返回值是HTTP响应,才检查状态 161 | if (response instanceof Response || 162 | // 更严格地检查是否为 axios 响应 163 | (response?.status !== undefined && 164 | response?.config !== undefined && // axios 响应特有的 config 属性 165 | response?.headers !== undefined)) { 166 | const isSuccess = await checkResponse(response); 167 | if (!isSuccess.ok) { 168 | throw new Error(isSuccess.error); 169 | } 170 | } 171 | 172 | return response; 173 | 174 | } catch (error) { 175 | // 获取友好的错误描述 176 | const errorMessage = getNetworkErrorMessage(error); 177 | 178 | // 最后一次重试失败,记录错误并抛出 179 | if (attempt === maxRetries) { 180 | notificationManager.error({ 181 | "message": `${message}失败`, 182 | "context": { 183 | ...context, 184 | "达到最大重试次数": maxRetries, 185 | "原因": errorMessage 186 | } 187 | }); 188 | throw new Error(errorMessage); 189 | } 190 | 191 | // 记录警告并准备重试 192 | notificationManager.warning({ 193 | "message": `${message}失败`, 194 | "context": { 195 | ...context, 196 | "重试":`准备第${attempt + 1}次重试`, 197 | "原因": errorMessage 198 | } 199 | }); 200 | 201 | // 对于429错误(请求过多),增加等待时间 202 | let waitTime = delay; 203 | if (error.response && error.response.status === 429) { 204 | // 指数退避策略,每次重试等待时间翻倍 205 | waitTime = delay * Math.pow(2, attempt - 1); 206 | console.log(`检测到请求频率限制(429),等待${waitTime/1000}秒后重试...`); 207 | } 208 | 209 | await new Promise(resolve => setTimeout(resolve, waitTime)); 210 | } 211 | } 212 | } 213 | 214 | /** 215 | * 检查响应是否成功 216 | * @param {Response|Object} response - fetch的Response对象或axios的响应对象 217 | * @returns {Promise<{ok: boolean, error?: string}>} 检查结果 218 | * 219 | * fetch Response 对象结构: 220 | * { 221 | * ok: boolean, // 如果状态码在 200-299 之间则为 true 222 | * status: number, // HTTP 状态码 如 200, 404, 500 223 | * statusText: string, // 状态描述 如 "OK", "Not Found" 224 | * headers: Headers, // 响应头 225 | * body: ReadableStream, // 响应体(流) 226 | * // 方法 227 | * json(): Promise, // 解析 JSON 228 | * text(): Promise // 解析文本 229 | * } 230 | * 231 | * axios Response 对象结构: 232 | * { 233 | * data: any, // 响应数据,已经自动解析好了 234 | * status: number, // HTTP 状态码 235 | * statusText: string, // 状态描述 236 | * headers: Object, // 响应头 237 | * config: Object, // axios 配置 238 | * request: Object // 请求对象 239 | * } 240 | */ 241 | async function checkResponse(response) { 242 | // 统一获取状态码和状态文本 243 | const getStatusInfo = (response) => { 244 | // 处理 fetch 响应 245 | if (response instanceof Response) { 246 | return { 247 | status: response.status, 248 | statusText: response.statusText 249 | }; 250 | } 251 | // 处理 axios 正常响应 252 | if (response?.status !== undefined) { 253 | return { 254 | status: response.status, 255 | statusText: response.statusText 256 | }; 257 | } 258 | return null; 259 | }; 260 | 261 | // 检查响应状态 262 | const statusInfo = getStatusInfo(response); 263 | if (!statusInfo) { 264 | return { 265 | ok: false, 266 | error: '非标准HTTP响应' 267 | }; 268 | } 269 | 270 | const { status, statusText } = statusInfo; 271 | const isSuccess = status >= 200 && status < 300; 272 | 273 | if (!isSuccess) { 274 | return { 275 | ok: false, 276 | error: getHttpErrorMessage(status, statusText) 277 | }; 278 | } 279 | 280 | return { ok: true }; 281 | } -------------------------------------------------------------------------------- /projects/README.md: -------------------------------------------------------------------------------- 1 | 此模块是项目模块,用于开发一些热门项目的脚本,具有时效性。 2 | 3 | web3项目交互过程中,难免会掺杂一些web2的请求。比如web3交互的某个参数需要请求项目方的服务器拿到,为了拿到这个参数还需要更多的请求。这篇文章很好的还原了这个过程,学会这个思路,一通百通 4 | 5 | 抽丝剥茧:实战 Web3 交互中如何搞定签名:https://x.com/gm365/status/1733019500025663914 -------------------------------------------------------------------------------- /projects/monad/faucet.js: -------------------------------------------------------------------------------- 1 | import { generateUUID } from "../../packages/utils-module/utils.js"; 2 | import { captchaManager } from "../../packages/utils-module/captcha.js"; 3 | import { notificationManager } from "../../packages/notification-module/notification.js"; 4 | import axios from 'axios'; 5 | import { withRetry } from "../../packages/utils-module/retry.js"; 6 | import { SocksProxyAgent } from 'socks-proxy-agent'; 7 | 8 | export async function faucet({ chromeNumber, address, socksProxyUrl }) { 9 | try { 10 | notificationManager.info({ 11 | message: `monad开始领水`, 12 | context: { 13 | "账号": chromeNumber, 14 | "地址": address, 15 | } 16 | }); 17 | 18 | const href = 'https://testnet.monad.xyz/'; 19 | const baseProxy = socksProxyUrl.replace(/^socks5:\/\//i, ''); 20 | 21 | // vercel验证码 22 | // const { _vcrcs, extra } = await captchaManager.verifyWebsite({ 23 | // captchaService: 'noCaptcha', 24 | // captchaType: 'vercel', 25 | // taskVariant: 'universal', 26 | // href, 27 | // proxy: baseProxy, 28 | // user_agent: fingerprint.userAgent, 29 | // }); 30 | 31 | // CloudflareTurnstile验证码 32 | // const { token: cfToken, userAgent } = await captchaManager.verifyWebsite({ 33 | // captchaService: 'capSolver', 34 | // captchaType: 'cloudflareTurnstile', 35 | // taskVariant: 'standard', 36 | // websiteURL: href, 37 | // websiteKey: '0x4AAAAAAA-3X4Nd7hf3mNGx', 38 | // }); 39 | 40 | // CloudflareTurnstile验证码 41 | const { token: cfToken, extra } = await captchaManager.verifyWebsite({ 42 | captchaService: 'noCaptcha', 43 | captchaType: 'cloudflareTurnstile', 44 | taskVariant: 'universal', 45 | href, 46 | sitekey: '0x4AAAAAAA-3X4Nd7hf3mNGx', 47 | proxy: baseProxy 48 | }); 49 | 50 | const headers = { 51 | ...extra, 52 | "accept": "*/*", 53 | "accept-encoding": "gzip, deflate, br, zstd", 54 | "accept-language": "zh-CN,zh;q=0.9", 55 | "content-type": "application/json", 56 | "origin": "https://testnet.monad.xyz", 57 | "priority": "u=1, i", 58 | "referer": "https://testnet.monad.xyz/", 59 | } 60 | 61 | // const headers = { 62 | // "accept": "*/*", 63 | // "accept-encoding": "gzip, deflate, br, zstd", 64 | // "accept-language": "zh-CN,zh;q=0.9", 65 | // // "accept-language": extra["accept-language"], 66 | // "content-type": "application/json", 67 | // "origin": "https://testnet.monad.xyz", 68 | // "priority": "u=1, i", 69 | // "referer": "https://testnet.monad.xyz/", 70 | // "sec-ch-ua": fingerprint.headers["sec-ch-ua"], 71 | // // "sec-ch-ua": extra["sec-ch-ua"], 72 | // "sec-ch-ua-mobile": "?0", 73 | // "sec-ch-ua-platform": "macOS", 74 | // // "sec-ch-ua-platform": extra["sec-ch-ua-platform"], 75 | // "sec-fetch-dest": "empty", 76 | // "sec-fetch-mode": "cors", 77 | // "sec-fetch-site": "same-origin", 78 | // "user-agent": fingerprint.userAgent, 79 | // // "user-agent": extra["user-agent"], 80 | // // "cookie": `_vcrcs=${_vcrcs}`, 81 | // } 82 | 83 | const jsonData = { 84 | "address": address, 85 | "cloudFlareResponseToken": cfToken, 86 | "visitorId": generateUUID(false) 87 | } 88 | 89 | // tls接口 90 | // await captchaManager.verifyWebsite({ 91 | // captchaService: 'noCaptcha', 92 | // captchaType: 'tls', 93 | // taskVariant: 'universal', 94 | // url: `${href}api/claim`, 95 | // method: 'post', 96 | // headers, 97 | // proxy: baseProxy, 98 | // json: jsonData, 99 | // http2: true, 100 | // timeout: 30, 101 | // }); 102 | 103 | const result = await withRetry( 104 | async () => { 105 | // console.log('monad领水参数', jsonData); 106 | const response = await axios.post(`${href}api/faucet/claim`, jsonData, { 107 | headers, 108 | httpsAgent: new SocksProxyAgent(socksProxyUrl), 109 | }); 110 | return response.data; 111 | }, 112 | { 113 | message: 'monad领水', 114 | context: { 115 | "账号": chromeNumber, 116 | "地址": address, 117 | } 118 | } 119 | ) 120 | 121 | notificationManager.success({ 122 | message: `monad领水成功`, 123 | context: { 124 | "账号": chromeNumber, 125 | "地址": address, 126 | "结果": result, 127 | } 128 | }); 129 | } catch (error) { 130 | notificationManager.error({ 131 | message: `monad领水失败`, 132 | context: { 133 | "账号": chromeNumber, 134 | "地址": address, 135 | "错误": error, 136 | } 137 | }); 138 | } 139 | } -------------------------------------------------------------------------------- /projects/saharaAi/faucet.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { HttpsProxyAgent } from 'https-proxy-agent'; 3 | import { withRetry } from '../../packages/utils-module/retry.js'; 4 | import { captchaManager } from '../../packages/utils-module/captcha.js'; 5 | import { notificationManager } from '../../packages/notification-module/notification.js'; 6 | 7 | // 注意eth主网余额 >=0.01eth 才能领到水 8 | export async function faucet({ chromeNumber, address, httpProxyUrl, fingerprint }) { 9 | try { 10 | 11 | notificationManager.info({ 12 | message: `saharaAi开始领水`, 13 | context: { 14 | "账号": chromeNumber, 15 | "地址": address, 16 | } 17 | }); 18 | // 创建 HTTP 代理实例 19 | const agent = new HttpsProxyAgent(httpProxyUrl); 20 | 21 | const { token: cfToken } = await captchaManager.verifyWebsite({ 22 | captchaService: 'capSolver', 23 | captchaType: 'cloudflareTurnstile', 24 | taskVariant: 'standard', 25 | websiteURL: 'https://faucet.saharalabs.ai/', 26 | websiteKey: '0x4AAAAAAA8hNPuIp1dAT_d9' 27 | });; 28 | if (!cfToken) { console.log('验证码获取失败'); return false; } 29 | 30 | await withRetry( 31 | async () => { 32 | const response = await axios.post('https://faucet-api.saharaa.info/api/claim2', 33 | { address }, 34 | { 35 | headers: { 36 | 'accept': '*/*', 37 | 'accept-language': 'en-US,en;q=0.9', 38 | 'content-type': 'application/json', 39 | 'cf-turnstile-response': cfToken, 40 | 'origin': 'https://faucet.saharalabs.ai', 41 | 'priority': 'u=1, i', 42 | 'referer': 'https://faucet.saharalabs.ai/', 43 | 'sec-ch-ua': fingerprint.headers['sec-ch-ua'], 44 | 'sec-ch-ua-mobile': '?0', 45 | 'sec-ch-ua-platform': 'macOS', 46 | 'sec-fetch-dest': 'empty', 47 | 'sec-fetch-mode': 'cors', 48 | 'sec-fetch-site': 'cross-site', 49 | 'user-agent': fingerprint.userAgent 50 | }, 51 | httpsAgent: agent, 52 | timeout: 30000 53 | } 54 | ); 55 | 56 | if (response.status === 200) { 57 | notificationManager.success({ 58 | "message": `saharaAi领水成功`, 59 | "context": { 60 | "账号": chromeNumber, 61 | "地址": address, 62 | } 63 | }); 64 | return true; 65 | } 66 | }, 67 | { 68 | maxRetries: 5, 69 | delay: 2000, 70 | taskName: 'saharaAi领水', 71 | } 72 | ); 73 | } catch (error) { 74 | notificationManager.error({ 75 | "message": `saharaAi领水失败`, 76 | "context": { 77 | "账号": chromeNumber, 78 | "地址": address, 79 | "错误": error.message 80 | } 81 | }); 82 | } 83 | } --------------------------------------------------------------------------------