├── log.png ├── alert.png ├── requirements.txt ├── config.ini ├── LICENSE ├── README.md ├── blur_ding.py └── blur_telegram.py /log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoyeETH/blur_bid_alert/HEAD/log.png -------------------------------------------------------------------------------- /alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoyeETH/blur_bid_alert/HEAD/alert.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | DingtalkChatbot==1.5.7 2 | selenium==4.3.0 3 | requests~=2.27.1 4 | web3~=5.31.3 5 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [config] 2 | # Chrome路径 可以按照多开浏览器的方法复制一份到新的路径 3 | path = D:\ChromeSelenium 4 | # MetaMask密码 5 | password = AAAAAAAAAA 6 | # 要监控的地址(替换为自己地址) 全小写 7 | address = 0x777777777b35977762b9259df1dc8880471f898 8 | # 预警参数 9 | # 当bid位于第一档时且第一档剩余bid总数低于limit0 10 | # 当bid位于第二档时且第一档剩余bid总数低于limit1 11 | # 当bid位于第三档时且第一档与第二档剩余bid总数低于limit2 12 | limit0 = 20 13 | limit1 = 10 14 | limit2 = 10 15 | # 每轮循环的间隔时间 单位: 秒 16 | cd = 60 17 | # 触发提醒时自动取消bid,填on时开启 18 | cancel = on 19 | # 取消后自动以低于原来bid价0.01e的价格补回bid,填on时开启 20 | rebid = off 21 | # 自动bid时签名,填on时开启 22 | sign = off 23 | # 监控合约alert_contract最高出价达到alert_price,发送提醒,接盘后专用 24 | alert_contract = 25 | alert_price = 26 | # 钉钉和telegram只要填一组就行 27 | # 钉钉群组添加自定义机器人,选择加签模式,得到的webhook和secret 28 | webhook = https://oapi.dingtalk.com/robot/send?access_token=8888888888888888e3a9e53781405ce39c9a6e7e7ff7c5cb19c687bb754a 29 | secret = SEC88888888888882ee825f4720b74b31edd05b0538eda27dac4fcdd 30 | # telegram配置 botFather创建机器人 31 | telegram_bot_token = 88888888:AAAAAAAADmjA_Qr54lRRXU0lYAakOUDAyAY 32 | telegram_id = 345678912 33 | # 白名单列表 在这部分的合约不会触发提醒 34 | whitelist = 35 | # 黑名单列表 在这部分的合约不会自动bid 36 | blacklist = 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 luoye.eth 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 | # blur_bid_alert 2 | Blur bid提醒/自动撤单 使用selenium实现 3 | 4 | > Blur里面割韭菜的太多,目前我自己刷分亏麻,这个项目不准备维护了,最新版本已经全部开源 5 | 6 | ## 使用教程 7 | 8 | ### Release版本 9 | 下载压缩包 并按教程配置 10 | 11 | ### 源码 12 | 13 | ### 环境要求 14 | * Windows 或 Mac 操作系统 15 | * Python 3.10 16 | * Chrome 浏览器 17 | 18 | ### 安装 Python 19 | #### Windows 20 | 访问 https://www.python.org/downloads/windows/ 下载 Windows 版本的 Python 安装程序。 21 | 运行安装程序,根据提示安装 Python。 22 | #### Mac 23 | 访问 https://www.python.org/downloads/mac-osx/ 下载 Mac 版本的 Python 安装程序。 24 | 运行安装程序,根据提示安装 Python。 25 | 26 | ### 安装 PyCharm 27 | PyCharm 是一个 Python 集成开发环境(IDE),可以方便地编写和调试 Python 代码。如果你已经有了其他的 Python 开发环境,可以跳过这一步。 28 | 29 | 访问 https://www.jetbrains.com/pycharm/download/ 下载 PyCharm 安装程序。 30 | 运行安装程序,根据提示安装 PyCharm。 31 | 32 | ### 安装浏览器驱动 33 | 本程序使用 Chrome 浏览器进行自动化操作,需要下载对应版本的 Chrome 驱动。请根据你的 Chrome 浏览器版本下载对应版本的驱动,并将驱动放在程序根目录下。 34 | 35 | 打开 Chrome 浏览器,在地址栏中输入 chrome://settings/help,查看自己的 Chrome 版本号。 36 | 访问 https://chromedriver.chromium.org/downloads 下载对应版本的 Chrome 驱动。 37 | 将下载好的驱动文件解压缩,将 chromedriver.exe(Windows)或 chromedriver(Mac)文件放在程序根目录下。 38 | 39 | ### 安装依赖 40 | 本程序依赖于一些 Python 包,可以通过以下命令安装: 41 | 42 | `pip install -r requirements.txt` 43 | 44 | ### 填写配置文件 45 | 程序需要读取 config.ini 配置文件中的参数,根据需要修改字段 46 | 47 | ## FAQ 持续更新 48 | 49 | * 1.检查chrome版本与下载 50 | 检查自己浏览器版本: chrome://settings/help 51 | 下载地址: https://chromedriver.chromium.org/downloads 52 | 53 | * 2.钉钉机器人创建路径 54 | 电脑版钉钉,创建钉钉群,在钉钉群内 设置-智能群助手-添加机器人-选自定义机器人 添加时的安全设置选择加签 55 | 56 | * 3.多开浏览器 57 | 在Chrome地址栏输入chrome://version/,查看自己的“个人资料路径” 58 | 注意: 59 | 1)路径到User Data ,不要后面的Default 60 | 2)关闭所有的Chrome进程(必须关闭在运行的浏览器) 61 | 完整复制这个路径到一个新的路径 比如D:\ChromeSelenium 62 | 最后将这个路径填入config.ini的path中 63 | 以上过程比较方便,也可以自己创一个新的空浏览器再添加matamask导入助记词 64 | 65 | * 4.telegram机器人创建 66 | [BotFather](https://t.me/BotFather)-`/newBot` 67 | [MCT-Bot](https://t.me/MCT_CLUB_BOT)-`/getchatid` 获取用户id 68 | 机器人创建好后先发一个`/start`激活 69 | 70 | * 5.为什么执行成功可以看到积分但是bid列表为空? 71 | config.ini 文件中address的地址应为全小写地址 72 | 73 | ## 效果 74 | ![log](log.png) 75 | ![alert](alert.png) 76 | 77 | ## 免责声明 78 | 79 | **使用本软件及服务所存在的风险将完全由用户自行承担,本软件及服务不作任何类型的担保。用户理解和同意,本软件及服务可能会受到干扰、出现延迟或其他不可预见的情况,因使用本软件及服务而遭受的任何损失,本软件及服务不承担任何责任。对于因不可抗力或本软件及服务不能控制的原因造成的网络服务中断或其他缺陷,本软件及服务不承担任何责任。** 80 | 81 | **本软件及服务所包含或提供的所有内容、信息、软件、产品及服务,均由第三方(如广告商、用户等)提供或制造,本软件及服务对其合法性概不负责,亦不承担任何法律责任。** 82 | 83 | **本软件及服务对任何直接、间接、偶然、特殊及继起的损害不负任何责任,这些损害可能来自于:不当使用本软件及服务、在网上进行交易、非法使用本软件及服务或依赖本软件及服务所产生的损失及利润损失等。** 84 | 85 | **本软件及服务会定期更新和升级,因此本软件及服务可能会因此而改变或暂停,用户知晓并同意本软件及服务会变更或暂停的风险。** 86 | 87 | **用户在使用本软件及服务时,应遵守所有适用于其所在国家或地区的法律法规,用户理解并承担因违反法律法规所造成的后果。** 88 | 89 | **若用户对本免责声明或本软件及服务有任何疑问或意见,请及时联系我们。** 90 | 91 | ## 开源许可 92 | 93 | ```MIT License 94 | 95 | Copyright (c) [2023] [luoye.eth] 96 | 97 | Permission is hereby granted, free of charge, to any person obtaining a copy 98 | of this software and associated documentation files (the "Software"), to deal 99 | in the Software without restriction, including without limitation the rights 100 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 101 | copies of the Software, and to permit persons to whom the Software is 102 | furnished to do so, subject to the following conditions: 103 | 104 | The above copyright notice and this permission notice shall be included in all 105 | copies or substantial portions of the Software. 106 | 107 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 108 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 109 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 110 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 111 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 112 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 113 | SOFTWARE. 114 | ``` 115 | -------------------------------------------------------------------------------- /blur_ding.py: -------------------------------------------------------------------------------- 1 | # 钉钉推送版本不再更新 2 | import json 3 | from selenium import webdriver 4 | import time 5 | 6 | from selenium.common import NoSuchWindowException 7 | from selenium.webdriver.chrome.service import Service 8 | from selenium.webdriver.common.by import By 9 | from dingtalkchatbot.chatbot import DingtalkChatbot 10 | import os 11 | import sys 12 | import configparser 13 | import traceback 14 | 15 | try: 16 | # 获取可执行文件所在目录的绝对路径 17 | base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) 18 | 19 | # 读取INI配置文件 20 | config_path = os.path.join(base_path, 'config.ini') 21 | 22 | # 创建ConfigParser对象 23 | config = configparser.ConfigParser() 24 | 25 | # 读取配置文件 26 | config = configparser.ConfigParser() 27 | with open('config.ini', 'r', encoding='utf-8') as f: 28 | config.read_file(f) 29 | 30 | # 获取配置信息 31 | path = config.get('config', 'path') 32 | password = config.get('config', 'password') 33 | address = config.get('config', 'address') 34 | limit1 = int(config.get('config', 'limit1')) 35 | limit2 = int(config.get('config', 'limit2')) 36 | cd = int(config.get('config', 'cd')) 37 | webhook = config.get('config', 'webhook') 38 | secret = config.get('config', 'secret') 39 | whitelist_str = config.get('config', 'whitelist') 40 | whitelist = [i.strip() for i in whitelist_str.split(',')] 41 | 42 | 43 | def launchSeleniumWebdriver(): 44 | global driver 45 | option = webdriver.ChromeOptions() 46 | s = Service("chromedriver.exe") 47 | option.add_argument("--user-data-dir=" + path) 48 | driver = webdriver.Chrome(options=option, service=s) # 此时将webdriver.exe 保存到python Script目录下 49 | 50 | return driver 51 | 52 | 53 | def conncetMetaMask(): 54 | driver.execute_script("window.open();") 55 | driver.switch_to.window(driver.window_handles[1]) 56 | EXTENSION_ID = 'nkbihfbeogaeaoehlefnkodbefgpgknn' 57 | driver.get('chrome-extension://{}/home.html'.format(EXTENSION_ID)) 58 | inputs = driver.find_elements(By.XPATH, '//input') 59 | inputs[0].send_keys(password) 60 | driver.find_element(By.XPATH, '//button[text()="登录"]').click() 61 | driver.close() 62 | driver.switch_to.window(driver.window_handles[0]) 63 | time.sleep(2) 64 | 65 | 66 | def getBids(): 67 | driver.execute_script("window.open();") 68 | driver.switch_to.window(driver.window_handles[1]) 69 | driver.get(f'https://core-api.prod.blur.io/v1/collection-bids/user/{address}') 70 | time.sleep(1) 71 | json_data = driver.find_element(By.XPATH, '//pre').text 72 | driver.close() 73 | driver.switch_to.window(driver.window_handles[0]) 74 | time.sleep(1) 75 | return json_data 76 | 77 | 78 | def getRealtimeBidPool(url): 79 | driver.execute_script("window.open();") 80 | driver.switch_to.window(driver.window_handles[1]) 81 | driver.get(url) 82 | json_data = driver.find_element(By.XPATH, '//pre').text 83 | driver.close() 84 | driver.switch_to.window(driver.window_handles[0]) 85 | time.sleep(1) 86 | return json_data 87 | 88 | 89 | def getPoints(): 90 | driver.execute_script("window.open();") 91 | driver.switch_to.window(driver.window_handles[1]) 92 | driver.get("https://core-api.prod.blur.io/v1/user/rewards/wallet-compact") 93 | json_data = driver.find_element(By.XPATH, '//pre').text 94 | data_dict = json.loads(json_data) 95 | 96 | point_data = data_dict.get('wallet') 97 | print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 总积分: {point_data.get("bidTotalXp")}') 98 | driver.close() 99 | driver.switch_to.window(driver.window_handles[0]) 100 | time.sleep(1) 101 | 102 | 103 | if __name__ == '__main__': 104 | ding_bot = DingtalkChatbot(webhook, secret=secret) 105 | contract_whitelist = whitelist 106 | driver = launchSeleniumWebdriver() 107 | driver.implicitly_wait(5) 108 | driver.get('https://blur.io/portfolio/bids') 109 | conncetMetaMask() 110 | # 关闭其他窗口 111 | try: 112 | driver.switch_to.window(driver.window_handles[1]) 113 | driver.close() 114 | driver.switch_to.window(driver.window_handles[0]) 115 | except NoSuchWindowException: 116 | # 如果窗口不存在,则不执行任何操作 117 | pass 118 | 119 | while True: 120 | # 获取bid数据 121 | data = getBids() 122 | data_dict = json.loads(data) 123 | # 获取priceLevels列表 124 | price_levels = data_dict.get('priceLevels') 125 | # 只保留priceLevels中的executableSize大于0的数据 126 | result = [] 127 | for level in price_levels: 128 | if level.get('executableSize', 0) > 0: 129 | result.append({'contractAddress': level['contractAddress'], 'price': level['price']}) 130 | 131 | # 打印结果 132 | print(result) 133 | 134 | # 获取url列表 135 | url_list = [] 136 | for i in result: 137 | url = 'https://core-api.prod.blur.io/v1/collections/' + i['contractAddress'] + '/executable-bids' 138 | url_list.append(url) 139 | 140 | for i in range(0, len(url_list)): 141 | price = result[i]['price'] 142 | contractAddress = result[i]['contractAddress'] 143 | bidpool_data = getRealtimeBidPool(url_list[i]) 144 | data_dict = json.loads(bidpool_data) 145 | # 获取bidpool列表 146 | bid_pool = data_dict.get('priceLevels') 147 | num_levels = len(bid_pool) 148 | if num_levels == 1: 149 | executableSize1 = bid_pool[0]['executableSize'] 150 | executableSize2 = 9999 151 | else: 152 | executableSize1 = bid_pool[0]['executableSize'] 153 | executableSize2 = bid_pool[1]['executableSize'] 154 | if bid_pool[0]['price'] == price: 155 | msg = f"{contractAddress} 第一档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2}\n直达链接: " \ 156 | f"https://blur.io/portfolio/bids?contractAddress={contractAddress} " 157 | print("\033[31m" + msg + "\033[0m") 158 | if contract_whitelist.count(contractAddress) == 0: 159 | ding_bot.send_text(msg) 160 | elif bid_pool[1]['price'] == price: 161 | msg = f"{contractAddress} 第二档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2}\n直达链接: " \ 162 | f"https://blur.io/portfolio/bids?contractAddress={contractAddress} " 163 | print(msg) 164 | if executableSize1 < limit1 and contract_whitelist.count(contractAddress) == 0: 165 | ding_bot.send_text(msg) 166 | elif bid_pool[2]['price'] == price: 167 | msg = f"{contractAddress} 第三档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2}\n直达链接: " \ 168 | f"https://blur.io/portfolio/bids?contractAddress={contractAddress} " 169 | print(msg) 170 | if executableSize1 < limit1 and executableSize2 < limit2 and contract_whitelist.count( 171 | contractAddress) == 0: 172 | ding_bot.send_text(msg) 173 | else: 174 | print(f"{contractAddress} 三档开外 安全") 175 | time.sleep(1) 176 | getPoints() 177 | driver.refresh() 178 | print(f"{cd}秒后开始新一轮检查") 179 | time.sleep(cd) 180 | 181 | print("done") 182 | driver.quit() 183 | 184 | except Exception as e: 185 | # 捕获异常并打印错误信息 186 | print('Error: ') 187 | print(traceback.format_exc()) 188 | input('Press Enter to exit...') 189 | 190 | 191 | -------------------------------------------------------------------------------- /blur_telegram.py: -------------------------------------------------------------------------------- 1 | import json 2 | from selenium import webdriver 3 | import time 4 | import re 5 | from web3 import Web3 6 | from selenium.common import NoSuchWindowException, NoSuchElementException 7 | from selenium.webdriver.chrome.service import Service 8 | from selenium.webdriver.common.by import By 9 | import os 10 | import sys 11 | import configparser 12 | import traceback 13 | import requests 14 | from decimal import Decimal 15 | 16 | global driver 17 | restart = False 18 | while True: 19 | try: 20 | # 获取可执行文件所在目录的绝对路径 21 | base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) 22 | 23 | # 读取INI配置文件 24 | config_path = os.path.join(base_path, 'config.ini') 25 | 26 | # 创建ConfigParser对象 27 | config = configparser.ConfigParser() 28 | 29 | # 读取配置文件 30 | config = configparser.ConfigParser() 31 | with open('config.ini', 'r', encoding='utf-8') as f: 32 | config.read_file(f) 33 | 34 | # 获取配置信息 35 | path = config.get('config', 'path') 36 | password = config.get('config', 'password') 37 | address = config.get('config', 'address') 38 | if re.match("^0x[0-9a-fA-F]{40}$", address) and address == address.lower(): 39 | pass 40 | else: 41 | print("The address is not valid or not all lowercase.") 42 | raise Exception("地址填写错误") 43 | limit0 = int(config.get('config', 'limit0')) 44 | limit1 = int(config.get('config', 'limit1')) 45 | limit2 = int(config.get('config', 'limit2')) 46 | cd = int(config.get('config', 'cd')) 47 | cancel = config.get('config', 'cancel') 48 | sign = config.get('config', 'sign') 49 | rebid = config.get('config', 'rebid') 50 | alert_contract_str = config.get('config', 'alert_contract') 51 | alert_contract = [i.strip() for i in alert_contract_str.split(',')] 52 | alert_price_str = config.get('config', 'alert_price') 53 | if not alert_price_str == "": 54 | alert_price = [float(x) for x in alert_price_str.split(',')] 55 | else: 56 | alert_price = [] 57 | telegram_bot_token = config.get('config', 'telegram_bot_token') 58 | telegram_id = config.get('config', 'telegram_id') 59 | whitelist_str = config.get('config', 'whitelist') 60 | whitelist_array = [i.strip() for i in whitelist_str.split(',')] 61 | whitelist = [s.lower() for s in whitelist_array] 62 | blacklist_str = config.get('config', 'blacklist') 63 | blacklist_array = [i.strip() for i in blacklist_str.split(',')] 64 | blacklist = [s.lower() for s in blacklist_array] 65 | 66 | 67 | def launchSeleniumWebdriver(): 68 | 69 | option = webdriver.ChromeOptions() 70 | s = Service("chromedriver.exe") 71 | option.add_argument("--user-data-dir=" + path) 72 | option.add_argument('log-level=INT') 73 | # option.add_argument('--headless') 74 | 75 | driver = webdriver.Chrome(options=option, service=s) # 此时将webdriver.exe 保存到python Script目录下 76 | 77 | return driver 78 | 79 | 80 | def conncetMetaMask(): 81 | driver.execute_script("window.open();") 82 | driver.switch_to.window(driver.window_handles[1]) 83 | EXTENSION_ID = 'nkbihfbeogaeaoehlefnkodbefgpgknn' 84 | driver.get('chrome-extension://{}/home.html'.format(EXTENSION_ID)) 85 | inputs = driver.find_elements(By.XPATH, '//input') 86 | inputs[0].send_keys(password) 87 | driver.find_element(By.XPATH, '//button[text()="登录"]').click() 88 | driver.close() 89 | driver.switch_to.window(driver.window_handles[0]) 90 | time.sleep(2) 91 | 92 | 93 | def checkElement(xpath): 94 | try: 95 | element = driver.find_element(By.XPATH, xpath) 96 | except NoSuchElementException: 97 | # print(f"no element {xpath}") 98 | return False 99 | else: 100 | return True 101 | 102 | 103 | def getBids(): 104 | driver.execute_script("window.open();") 105 | driver.switch_to.window(driver.window_handles[1]) 106 | driver.get(f'https://core-api.prod.blur.io/v1/collection-bids/user/{address}') 107 | time.sleep(1) 108 | json_data = driver.find_element(By.XPATH, '//pre').text 109 | driver.close() 110 | driver.switch_to.window(driver.window_handles[0]) 111 | time.sleep(1) 112 | return json_data 113 | 114 | 115 | def getRealtimeBidPool(url): 116 | driver.execute_script("window.open();") 117 | driver.switch_to.window(driver.window_handles[1]) 118 | driver.get(url) 119 | json_data = driver.find_element(By.XPATH, '//pre').text 120 | driver.close() 121 | driver.switch_to.window(driver.window_handles[0]) 122 | return json_data 123 | 124 | 125 | def getPoints(): 126 | driver.execute_script("window.open();") 127 | driver.switch_to.window(driver.window_handles[1]) 128 | driver.get("https://core-api.prod.blur.io/v1/user/rewards/wallet-compact") 129 | json_data = driver.find_element(By.XPATH, '//pre').text 130 | data_dict = json.loads(json_data) 131 | 132 | point_data = data_dict.get('wallet') 133 | print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 总积分: {point_data.get("bidTotalXp")}') 134 | driver.close() 135 | driver.switch_to.window(driver.window_handles[0]) 136 | time.sleep(1) 137 | 138 | 139 | def tg_bot(message): 140 | TOKEN = telegram_bot_token 141 | chat_id = telegram_id 142 | # 发送带有链接的消息,并禁用链接预览 143 | url = f"https://api.telegram.org/bot{TOKEN}/sendMessage" 144 | params = {"chat_id": chat_id, "text": message, "disable_web_page_preview": True} 145 | response = requests.post(url, params=params) 146 | 147 | # 检查是否成功发送消息 148 | if response.status_code == 200: 149 | pass 150 | else: 151 | print(f"发送消息失败,错误代码:{response.status_code}") 152 | 153 | 154 | def cancel_bid(contract, price, size): 155 | if cancel == "on": 156 | driver.maximize_window() 157 | collectionSlug = getcollectionSlug(contract) 158 | driver.execute_script("window.open();") 159 | driver.switch_to.window(driver.window_handles[1]) 160 | driver.get(f"https://blur.io/portfolio/bids?contractAddress={contract}") 161 | flag = checkElement('//button[@title="cancel bid"]') 162 | time.sleep(3) 163 | if flag: 164 | driver.find_element(By.XPATH, '//button[@title="cancel bid"]').click() 165 | tg_bot(f"[取消BID]\n{collectionSlug} 已取消现有bid,请重新bid\n直达链接: https://blur.io/{contract}/bids") 166 | else: 167 | time.sleep(3) 168 | flag = checkElement('//button[@title="cancel bid"]') 169 | if flag: 170 | driver.find_element(By.XPATH, '//button[@title="cancel bid"]').click() 171 | tg_bot(f"[取消BID]\n{collectionSlug} 已取消现有bid,请重新bid\n直达链接: https://blur.io/collection/{contract}/bids") 172 | else: 173 | print("取消bid失败") 174 | time.sleep(1) 175 | driver.close() 176 | driver.switch_to.window(driver.window_handles[0]) 177 | driver.minimize_window() 178 | time.sleep(0.5) 179 | bid_price = str(Decimal(price) - Decimal("0.01")) 180 | bid_size = int(size) 181 | if bid_size > 100: 182 | bid_size = 100 183 | result = re_bid(collectionSlug, bid_price, bid_size) 184 | if result: 185 | tg_bot(f"[自动bid]\nbid项目{collectionSlug}成功\nbid价格: {bid_price} eth, bid数量: {bid_size} 个") 186 | 187 | 188 | def bid_price_alert(contract, price): 189 | url = 'https://core-api.prod.blur.io/v1/collections/' + contract + '/executable-bids' 190 | bidpool_data = getRealtimeBidPool(url) 191 | data_dict = json.loads(bidpool_data) 192 | # 获取bidpool列表 193 | bid_pool = data_dict.get('priceLevels') 194 | if float(bid_pool[0]['price']) >= price: 195 | tg_bot( 196 | f"[高价BID提示]\n{contract}出现 {price} eth及以上出价 \n" 197 | f"价格: {bid_pool[0]['price']} eth, 数量: {bid_pool[0]['executableSize']}个\n" 198 | f"确认BID: https://blur.io/collection/{contract}/bids\n" 199 | f"接受BID: https://blur.io/portfolio?contractAddress={contract}") 200 | 201 | 202 | def check_pass(addr): 203 | 204 | # 连接到以太坊节点 205 | w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/cvMn6fHiMcsyT82Ms5MRB6yBGcEciIez')) 206 | 207 | # 以太坊ERC721合约地址 208 | contract_address = '0xF3B866a14b7e80B7A7968413117CA8926b85602e' 209 | 210 | # ERC721合约ABI 211 | abi = [{ 212 | "constant": True, 213 | "inputs": [ 214 | { 215 | "name": "_owner", 216 | "type": "address" 217 | } 218 | ], 219 | "name": "balanceOf", 220 | "outputs": [ 221 | { 222 | "name": "", 223 | "type": "uint256" 224 | } 225 | ], 226 | "payable": False, 227 | "stateMutability": "view", 228 | "type": "function" 229 | }] 230 | 231 | # 实例化ERC721合约对象 232 | myContract = w3.eth.contract(contract_address, abi=abi) 233 | 234 | # 要验证的地址 235 | address_to_check = Web3.toChecksumAddress(addr) 236 | 237 | # 调用balanceOf函数获取地址持有的NFT数量 238 | nft_count = myContract.functions.balanceOf(address_to_check).call() 239 | 240 | # 验证地址是否持有至少一个指定NFT 241 | if nft_count > 0: 242 | print(f'地址 {address_to_check} Pass卡验证通过') 243 | return True 244 | else: 245 | print(f'地址 {address_to_check} 无效') 246 | return False 247 | 248 | 249 | def sign_bid(): 250 | if sign == "on": 251 | driver.execute_script("window.open();") 252 | driver.switch_to.window(driver.window_handles[2]) 253 | EXTENSION_ID = 'nkbihfbeogaeaoehlefnkodbefgpgknn' 254 | driver.get('chrome-extension://{}/home.html'.format(EXTENSION_ID)) 255 | flag = checkElement('//i') 256 | if flag: 257 | driver.find_element(By.XPATH, '//i').click() 258 | driver.find_element(By.XPATH, '//button[text()="签名"]').click() 259 | driver.close() 260 | driver.switch_to.window(driver.window_handles[1]) 261 | try: 262 | if len(driver.window_handles) > 2: 263 | driver.switch_to.window(driver.window_handles[2]) 264 | driver.close() 265 | driver.switch_to.window(driver.window_handles[1]) 266 | except NoSuchWindowException: 267 | # 如果窗口不存在,则不执行任何操作 268 | pass 269 | time.sleep(2) 270 | else: 271 | time.sleep(10) 272 | 273 | def bid(collectionSlug, price, size): 274 | try: 275 | driver.maximize_window() 276 | try: 277 | if len(driver.window_handles) > 1: 278 | driver.switch_to.window(driver.window_handles[1]) 279 | driver.close() 280 | driver.switch_to.window(driver.window_handles[0]) 281 | except NoSuchWindowException: 282 | # 如果窗口不存在,则不执行任何操作 283 | pass 284 | driver.execute_script("window.open();") 285 | driver.switch_to.window(driver.window_handles[1]) 286 | driver.get(f"https://blur.io/collection/{collectionSlug}/bids") 287 | time.sleep(1) 288 | flag = checkElement('//div[text()="place collection bid"]') 289 | if flag: 290 | driver.find_element(By.XPATH, '//div[text()="place collection bid"]').click() 291 | input1 = driver.find_element(By.XPATH, '//input[@placeholder="0.00"]') 292 | input1.send_keys(price) 293 | input2 = driver.find_element(By.XPATH, '//input[@placeholder="1"]') 294 | input2.clear() 295 | time.sleep(1) 296 | input2.click() 297 | input2.clear() 298 | input2.send_keys(str(size)) 299 | driver.find_element(By.XPATH, '//div[text()="place bid"]').click() 300 | time.sleep(5) 301 | sign_bid() 302 | driver.close() 303 | driver.switch_to.window(driver.window_handles[0]) 304 | return True 305 | except Exception: 306 | driver.close() 307 | print(traceback.format_exc()) 308 | driver.switch_to.window(driver.window_handles[0]) 309 | return False 310 | 311 | 312 | def re_bid(collectionSlug, price, size): 313 | if rebid == "on": 314 | try: 315 | driver.maximize_window() 316 | try: 317 | if len(driver.window_handles) > 1: 318 | driver.switch_to.window(driver.window_handles[1]) 319 | driver.close() 320 | driver.switch_to.window(driver.window_handles[0]) 321 | except NoSuchWindowException: 322 | # 如果窗口不存在,则不执行任何操作 323 | pass 324 | driver.execute_script("window.open();") 325 | driver.switch_to.window(driver.window_handles[1]) 326 | driver.get(f"https://blur.io/collection/{collectionSlug}/bids") 327 | time.sleep(1) 328 | flag = checkElement('//div[text()="place collection bid"]') 329 | if flag: 330 | driver.find_element(By.XPATH, '//div[text()="place collection bid"]').click() 331 | input1 = driver.find_element(By.XPATH, '//input[@placeholder="0.00"]') 332 | input1.send_keys(price) 333 | input2 = driver.find_element(By.XPATH, '//input[@placeholder="1"]') 334 | input2.clear() 335 | time.sleep(1) 336 | input2.click() 337 | input2.clear() 338 | input2.send_keys(str(size)) 339 | driver.find_element(By.XPATH, '//div[text()="place bid"]').click() 340 | time.sleep(5) 341 | sign_bid() 342 | driver.close() 343 | driver.switch_to.window(driver.window_handles[0]) 344 | return True 345 | except Exception: 346 | driver.close() 347 | print(traceback.format_exc()) 348 | driver.switch_to.window(driver.window_handles[0]) 349 | return False 350 | else: 351 | return False 352 | 353 | 354 | def getContract(collectionSlug): 355 | driver.execute_script("window.open();") 356 | driver.switch_to.window(driver.window_handles[1]) 357 | driver.get(f"https://core-api.prod.blur.io/v1/collections/{collectionSlug}") 358 | json_data = driver.find_element(By.XPATH, '//pre').text 359 | data_dict = json.loads(json_data) 360 | collection_data = data_dict.get('collection') 361 | time.sleep(0.5) 362 | driver.close() 363 | driver.switch_to.window(driver.window_handles[0]) 364 | return collection_data["contractAddress"] 365 | 366 | 367 | def getcollectionSlug(contract): 368 | driver.execute_script("window.open();") 369 | driver.switch_to.window(driver.window_handles[1]) 370 | driver.get(f"https://core-api.prod.blur.io/v1/collections/{contract}") 371 | json_data = driver.find_element(By.XPATH, '//pre').text 372 | data_dict = json.loads(json_data) 373 | collection_data = data_dict.get('collection') 374 | time.sleep(0.5) 375 | driver.close() 376 | driver.switch_to.window(driver.window_handles[0]) 377 | return collection_data["collectionSlug"] 378 | 379 | def auto_bid(balance, depth, day, level): 380 | driver.get('https://blur.io/collections') 381 | conncetMetaMask() 382 | try: 383 | if len(driver.window_handles) > 1: 384 | driver.switch_to.window(driver.window_handles[1]) 385 | driver.close() 386 | driver.switch_to.window(driver.window_handles[0]) 387 | except NoSuchWindowException: 388 | # 如果窗口不存在,则不执行任何操作 389 | pass 390 | driver.refresh() 391 | time.sleep(1) 392 | # div_element = driver.find_element(By.XPATH, '//div[@role="rowgroup"]') 393 | # 394 | # # 执行 JavaScript 代码,实现 div 内部滚动 395 | # driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", div_element) 396 | # time.sleep(1) 397 | collections = driver.find_elements(By.XPATH, "//a[contains(@href, '/collection/')]") 398 | collectionSlug_list = [] 399 | for i in range(0, len(collections)): 400 | route = collections[i].get_attribute('href') 401 | collectionSlug_list.append(route.replace("https://blur.io/collection/", "")) 402 | print(f"获取到的项目数{len(collectionSlug_list)}") 403 | success_count = 0 404 | for i in range(0, len(collectionSlug_list)): 405 | if success_count >= depth: 406 | break 407 | contractAddr = getContract(collectionSlug_list[i]) 408 | # 判断合约创建时间 409 | # 410 | # 411 | if contractAddr in blacklist: 412 | print(f"项目{collectionSlug_list[i]}在黑名单, 不进行bid") 413 | continue 414 | api_url = 'https://core-api.prod.blur.io/v1/collections/' + contractAddr + '/executable-bids' 415 | bidpool_data = getRealtimeBidPool(api_url) 416 | data_dict = json.loads(bidpool_data) 417 | # 获取bidpool列表 418 | bid_pool = data_dict.get('priceLevels') 419 | num_levels = len(bid_pool) 420 | if num_levels < level: 421 | print(f"项目{collectionSlug_list[i]}档位不足, 不进行bid") 422 | continue 423 | bid_price = float(bid_pool[level - 1]['price']) 424 | bid_size = int(balance // bid_price) 425 | if bid_size == 0: 426 | bid_price = balance 427 | bid_size = 1 428 | if bid_size > 100: 429 | bid_size = 100 430 | result = bid(collectionSlug_list[i], bid_price, bid_size) 431 | if result: 432 | success_count += 1 433 | tg_bot(f"[自动bid]\nbid项目{collectionSlug_list[i]}成功\nbid价格: {bid_price} eth, bid数量: {bid_size} 个") 434 | 435 | # tg_bot(f"[自动出价]\n自动bid完成,bid成功了{}项目") 436 | # driver.quit() 437 | 438 | 439 | def check_bid(contractAddress, price, size): 440 | url = f"https://core-api.prod.blur.io/v1/collections/{contractAddress}/executable-bids" 441 | bidpool_data = getRealtimeBidPool(url) 442 | data_dict = json.loads(bidpool_data) 443 | # 获取bidpool列表 444 | bid_pool = data_dict.get('priceLevels') 445 | num_levels = len(bid_pool) 446 | if num_levels == 1: 447 | executableSize1 = bid_pool[0]['executableSize'] 448 | executableSize2 = 9999 449 | else: 450 | executableSize1 = bid_pool[0]['executableSize'] 451 | executableSize2 = bid_pool[1]['executableSize'] 452 | if bid_pool[0]['price'] == price: 453 | msg = f"BID处于第一档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2} 合约: {contractAddress}" \ 454 | f"\n直达链接: https://blur.io/portfolio/bids?contractAddress={contractAddress}" 455 | print("\033[31m" + msg + "\033[0m") 456 | if executableSize1 < limit0 and contract_whitelist.count(contractAddress) == 0: 457 | tg_bot(f"[预警提示]\n" + msg) 458 | cancel_bid(contractAddress, price, size) 459 | elif bid_pool[1]['price'] == price: 460 | msg = f"BID处于第二档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2} 合约: {contractAddress}" \ 461 | f"\n直达链接: https://blur.io/portfolio/bids?contractAddress={contractAddress}" 462 | print(msg) 463 | if executableSize1 < limit1 and contract_whitelist.count(contractAddress) == 0: 464 | tg_bot(f"[预警提示]\n" + msg) 465 | cancel_bid(contractAddress, price, size) 466 | elif bid_pool[2]['price'] == price: 467 | msg = f"BID处于第三档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2} 合约: {contractAddress}" \ 468 | f"\n直达链接: https://blur.io/portfolio/bids?contractAddress={contractAddress}" 469 | print(msg) 470 | if (executableSize1 + executableSize2) < limit2 and contract_whitelist.count( 471 | contractAddress) == 0: 472 | tg_bot(f"[预警提示]\n" + msg) 473 | cancel_bid(contractAddress, price, size) 474 | elif bid_pool[3]['price'] == price: 475 | msg = f"BID处于第四档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2} 合约: {contractAddress}" \ 476 | f"\n直达链接: https://blur.io/portfolio/bids?contractAddress={contractAddress}" 477 | print(msg) 478 | elif bid_pool[4]['price'] == price: 479 | msg = f"BID处于第五档 价格: {price} 第一档数量: {executableSize1} 第二档数量: {executableSize2} 合约: {contractAddress}" \ 480 | f"\n直达链接: https://blur.io/portfolio/bids?contractAddress={contractAddress}" 481 | print(msg) 482 | else: 483 | print(f"BID处于五档以外 合约: {contractAddress}") 484 | 485 | time.sleep(0.5) 486 | 487 | 488 | if __name__ == '__main__': 489 | is_allow = check_pass(address) 490 | if not is_allow: 491 | raise Exception("缺少Pass卡") 492 | driver = launchSeleniumWebdriver() 493 | driver.implicitly_wait(5) 494 | if not restart: 495 | 496 | mode = input("1.检查bid情况\n" 497 | "2.自动bid\n" 498 | "请选择模式 (1 或 2): ") 499 | 500 | if mode == "2": 501 | balance = input("请输入Blur-pool余额 (单位: eth): ") 502 | balance = float(balance) 503 | depth = input("请输入要bid项目的数量: ") 504 | depth = int(depth) 505 | day = input("请输入nft已创建天数最小值: ") 506 | level = input("请输入要bid的档位: ") 507 | level = int(level) 508 | if level == "1": 509 | ask = input("第一档接盘风险很大,确认继续?[y/n]:") 510 | if not ask == "y": 511 | raise Exception("取消bid") 512 | print("开始自动bid") 513 | tg_bot(f"[连通性检查]\n开始自动bid") 514 | auto_bid(balance, depth, day, level) 515 | print("--------------------------------------------") 516 | print("自动bid完成 开始开始检查bid情况") 517 | else: 518 | print("开始检查bid情况") 519 | 520 | tg_bot(f"[连通性检查]\n查询冷却时间 {cd}s\n推送时自动撤单 {cancel == 'on'}\n撤单后补回bid {rebid == 'on'}\n自动签名 {sign == 'on'}\n" 521 | f"[预警逻辑]\n当bid位于第一档时且第一档剩余bid总数低于{limit0}\n当bid位于第二档时且第一档剩余bid总数低于{limit1}\n" 522 | f"当bid位于第三档时且第一档与第二档剩余bid总数低于{limit2}") 523 | contract_whitelist = whitelist 524 | driver.get('https://blur.io/portfolio/bids') 525 | conncetMetaMask() 526 | # 关闭其他窗口 527 | try: 528 | if len(driver.window_handles) > 1: 529 | driver.switch_to.window(driver.window_handles[1]) 530 | driver.close() 531 | driver.switch_to.window(driver.window_handles[0]) 532 | except NoSuchWindowException: 533 | # 如果窗口不存在,则不执行任何操作 534 | pass 535 | driver.minimize_window() 536 | while True: 537 | # 获取bid数据 538 | data = getBids() 539 | data_dict = json.loads(data) 540 | # 获取priceLevels列表 541 | price_levels = data_dict.get('priceLevels') 542 | # 只保留priceLevels中的executableSize大于0的数据 543 | result = [] 544 | for level in price_levels: 545 | if level.get('executableSize', 0) > 0: 546 | result.append({'contractAddress': level['contractAddress'], 547 | 'price': level['price'], 'executableSize': level['executableSize']}) 548 | 549 | # 打印结果 550 | print(result) 551 | 552 | # 获取url列表 553 | url_list = [] 554 | for i in result: 555 | url = 'https://core-api.prod.blur.io/v1/collections/' + i['contractAddress'] + '/executable-bids' 556 | url_list.append(url) 557 | 558 | for i in range(0, len(url_list)): 559 | contractAddress = result[i]['contractAddress'] 560 | price = result[i]['price'] 561 | size = result[i]['executableSize'] 562 | check_bid(contractAddress, price, size) 563 | 564 | getPoints() 565 | for contract, price in zip(alert_contract, alert_price): 566 | bid_price_alert(contract, price) 567 | driver.refresh() 568 | print(f"{cd}秒后开始新一轮检查") 569 | time.sleep(cd) 570 | 571 | except Exception as e: 572 | # 捕获异常并打印错误信息 573 | print('Error: ') 574 | print(traceback.format_exc()) 575 | driver.quit() 576 | restart = True 577 | continue 578 | --------------------------------------------------------------------------------