├── .gitignore ├── README.md ├── dialog.py ├── dialog.ui ├── doc ├── demo1.png ├── demo2.png ├── demo3.png └── question1.png ├── farmer.py ├── favicon.ico ├── gui.pyw ├── gui.spec ├── inject.js ├── install_depends.py ├── logger.py ├── main.py ├── main.spec ├── requirements.txt ├── res.py ├── settings.py ├── user.yml.example ├── utils.py └── waxjs.js /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /.idea 3 | /logs 4 | /data_dir 5 | user.yml 6 | /dist 7 | /build 8 | chromedriver.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OpenFarmer 2 | 3 | ### 一个免费、开源的农民世界 FarmersWorld 挂机脚本 4 | ![image](https://raw.githubusercontent.com/encoderlee/OpenFarmer/main/doc/demo3.png) 5 | ### 初衷 6 | 7 | 农民世界 https://farmersworld.io 的火热相信大家已经有目共睹 8 | 9 | 网上各种辅助脚本也是满天飞,可是2021年11月7日某脚本商实施的大面积盗号事件让广大farmer伤心和愤怒 10 | 11 | 于是我决定免费开源自己写的一个简陋的挂机脚本,没有华丽的界面,但是绝对安心可用 12 | 13 | 代码完全公开,不含任何二进制可执行文件,不含任何后门病毒,完全经得住检验 14 | 15 | 同时,欢迎大家提 BUG 和 Push 代码,不断的完善它 16 | 17 | 本项目使用 python3 + selenium 开发,界面使用pyqt6 18 | 19 | 跨平台运行,支持 Windows、Linux、MacOS 20 | 21 | 原理思路见:https://blog.csdn.net/CharlesSimonyi/article/details/121413962 22 | 23 | 也欢迎关注我的CSDN博客 24 | 25 | 欢迎加入 Telegram 群组反馈问题:https://t.me/OpenFarmer 26 | 27 | 欢迎加入微信群,加微信好友【lintan299】拉你进群 28 | 29 | ### 功能 30 | 1. 支持一台电脑上多开 31 | 2. 支持设置HTTP代理 32 | 3. 支持所有采集工具,支持养鸡、种地、养牛 33 | 4. 支持5%自动提现 34 | 5. 支持自动充值 35 | 6. 支持自动卖产出的作物 36 | 7. 支持会员卡的自动点击 37 | 8. 工具耐久不足自动修理(请准备好足够修理的金币) 38 | 9. 能量不足自动补充(请准备好足够的肉) 39 | 10. 支持自动建造(新号第一次建造 COOP 和 FARM PLOT需要点8次的操作) 40 | 11. 其它功能正在补充中。。。 41 | 42 | ### 用法一 43 | 嫌麻烦的同学可以直接在github页面右侧的【Releases】处下载最新的打包版本,该版本只支持windows 64位系统,建议在win10系统上运行,其它更老的系统比如Win7没测试过,可能有问题。 44 | 45 | 把压缩包里的目录解压出来,双击运行【gui.exe】即可,命令行版本可运行【main.exe】,使用命令行版本前需手工修改配置文件【user.yml】 46 | 47 | 配置文件【user.yml】怎么修改,可以参考【user.yml.example】里的说明,最好使用【Notepad++】编辑器,Windows自带的记事本处理换行有问题 48 | 49 | 对安全性有要求,喜欢捣鼓代码的,建议从源码运行,根据下面的步骤一步步来 50 | ### 用法二 51 | 1. git clone 源码到本地,或 Download ZIP 下载源码到本地 52 | 2. 下载安装python3 (版本须大于等于python3.7) 53 | 54 | 请到python官网下载最新版本: 55 | https://www.python.org/downloads/ 56 | 57 | 【注意】安装时请记得勾选【Add Python 3.10 to PATH】 58 | 3. 双击运行 【install_depends.py】 来安装依赖包,一台电脑只需要安装一次即可 59 | 【注意】安装依赖包前请关闭翻墙代理,关闭科学上网,不然无法从豆瓣pypi镜像站下载依赖包 60 | 4. 安装Chrome浏览器,并升级到最新版 61 | 5. 下载ChromeDriver,版本确保和Chrome版本一致 62 | https://chromedriver.chromium.org/downloads 63 | 64 | 比如我的Chrome版本是 97.0.4692.71 65 | 66 | 那么我就下载 ChromeDriver 97.0.4692.71 67 | 68 | 其实小版本不一致也没关系,大版本号97一致就行 69 | 70 | windows系统的话下载【chromedriver_win32.zip】 71 | 6. 将下载的 ChromeDriver 压缩包中的 chromedriver.exe 文件,解压到本项目的源码目录中(和 main.py 在一个目录中) 72 | 7. 双击 【gui.pyw】 运行脚本程序,程序如果异常退出,可以到 logs 文件夹下查看日志 73 | 8. 程序启动后,请在程序界面上输入你的WAX钱包账号(以.wam结尾的钱包地址),根据你的需求,勾选需要脚本自动处理的作物,比如你只种地,那么只需要勾选【种地】即可,当然,默认全部勾选也是可以的,不过你没有牛的话,脚本每次都要去扫描牛棚,效率低点。支持设置HTTP代理,填写代理后勾选【启用代理】。最后点击【启动】按钮,开始运行。 74 | 9. 程序启动后,会弹出一个Chrome窗口并自动打开 FarmersWorld 官网,第一次启动请手工登录游戏,登录成功后,脚本会开始自动化操作。 75 | 10. 如果需要手工操作,请勿在脚本打开的Chrome窗口中操作,脚本打开的Chrome窗口,最小化即可,尽量不要动它,需要手工操作的时候,请另开Chrome浏览器登录游戏,该游戏本身就可同时在多个浏览器中登录,不会把脚本Chrome中的游戏T下线 76 | 11. 注意,一个账号第一次运行脚本,脚本第一次自动收割农作物的时候,Chrome浏览器中可能会弹出WAX钱包授权窗口,并停在那里不动了,这个时候需要勾选自动确认交易,并同意交易,这样脚本以后就能自动处理了,其实和人工操作是一样的,第一次收割的时候,也要点自动同意交易,否则每次都要弹出授权窗口来,脚本只负责收割农作物,不处理授权的事情,是否自动授权取决于用户账号设置 77 | 12. 脚本多开,请把整个源码目录复制一份,在另外一个目录中双击运行 【gui.pyw】 启动第二个程序,以此类推,多开互不干扰 78 | 13. 正确关闭程序,请点击程序窗口右上角的X,稍等几秒钟便会关闭 79 | ### 命令行版本 80 | ![image](https://raw.githubusercontent.com/encoderlee/OpenFarmer/main/doc/demo1.png) 81 | 如果不喜欢GUI或有特殊需求的同学,可以运行【main.py】,在控制台中运行程序,这种方式启动的话,需要先手工修改【user.yml】中的配置参数,每个参数的含义参考【user.yml.example】 82 | 83 | 84 | wax_account: (wax账号,也就是wax钱包地址,以.wam结尾) 85 | 86 | use_proxy: 是否启用代理,true或false 87 | 88 | proxy: (http代理,格式为127.0.0.1:10809) 89 | 90 | 下面的(build、mining、chicken、plant、cow、mbs)分别对应建造、采集资源、养鸡、种地、养牛、会员点击,需要程序自动化的操作,设置为true,不需要程序自动化的操作,设置为false,比如你只种地的话,plant: true 即可,其它全部为false,这样减少不必要的网络操作,提高运行效率 91 | 92 | recover_energy: 500 (能量不够时恢复到多少能量,默认500,请准备足够的肉,程序不会自动去买肉) 93 | ### 常见问题 94 | 1.程序日志显示,已经成功喂鸡,成功浇水,成功采集了,为什么Chrome中的游戏界面上还是显示没有喂鸡,没有浇水,没有采集? 95 | 96 | 这是因为程序是通过直接调用智能合约的方式进行的操作,Chrome中游戏界面并不会自动更新,实际上只要日志显示操作成功,就已经操作成功了,Chrome中的游戏界面不更新,无需理会,你可以重新开一个Chrome窗口,重新登录游戏查看,到底操作成功了没有 97 | 98 | 2.无法使用google账号登录,提示此浏览器或应用可能不安全? 99 | 100 | ![image](https://raw.githubusercontent.com/encoderlee/OpenFarmer/main/doc/question1.png) 101 | 102 | 这是因为Chrome本身就是google家的,google判断到该Chrome浏览器正受程序控制,便判定为不安全,不允许登录。解决办法就是在WAX云钱包登录界面,点【Forgot Password】(忘记密码),输入google邮箱账号,根据提示重置密码(可以重置为和原来一样的密码),重置成功后,便可在WAX云钱包登录界面,直接输入google邮箱账号和重置后的密码进行登录,而不需要点google图标,不需要通过google账号登录。 103 | ### 打赏 104 | 欢迎打赏,支持我继续不断完善这个项目 105 | 106 | eth、bsc钱包地址: 107 | 108 | 0xeaC7d998684F50b7A492EA68F27633a117Be201d 109 | 110 | 支持USDT、ETH、BUSD、BNB等,以及 Ethereum、BSC、xDAI等eth兼容网络上的任何ERC20代币 111 | 112 | wax网络钱包地址: 113 | 114 | m45yy.wam 115 | -------------------------------------------------------------------------------- /dialog.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'dialog.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.2.2 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_Dialog(object): 13 | def setupUi(self, Dialog): 14 | Dialog.setObjectName("Dialog") 15 | Dialog.resize(775, 517) 16 | icon = QtGui.QIcon() 17 | icon.addPixmap(QtGui.QPixmap("C:/Users/Administrator/.designer/backup/favicon.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) 18 | Dialog.setWindowIcon(icon) 19 | self.label_1 = QtWidgets.QLabel(Dialog) 20 | self.label_1.setGeometry(QtCore.QRect(20, 17, 41, 16)) 21 | self.label_1.setObjectName("label_1") 22 | self.edit_account = QtWidgets.QLineEdit(Dialog) 23 | self.edit_account.setGeometry(QtCore.QRect(50, 15, 116, 20)) 24 | self.edit_account.setObjectName("edit_account") 25 | self.plain_text_edit = QtWidgets.QPlainTextEdit(Dialog) 26 | self.plain_text_edit.setGeometry(QtCore.QRect(20, 260, 741, 221)) 27 | self.plain_text_edit.setReadOnly(True) 28 | self.plain_text_edit.setMaximumBlockCount(100) 29 | self.plain_text_edit.setObjectName("plain_text_edit") 30 | self.button_start = QtWidgets.QPushButton(Dialog) 31 | self.button_start.setGeometry(QtCore.QRect(650, 30, 101, 41)) 32 | self.button_start.setObjectName("button_start") 33 | self.edit_proxy = QtWidgets.QLineEdit(Dialog) 34 | self.edit_proxy.setGeometry(QtCore.QRect(265, 15, 101, 20)) 35 | self.edit_proxy.setObjectName("edit_proxy") 36 | self.checkbox_proxy = QtWidgets.QCheckBox(Dialog) 37 | self.checkbox_proxy.setGeometry(QtCore.QRect(190, 15, 71, 20)) 38 | self.checkbox_proxy.setChecked(False) 39 | self.checkbox_proxy.setObjectName("checkbox_proxy") 40 | self.verticalLayoutWidget = QtWidgets.QWidget(Dialog) 41 | self.verticalLayoutWidget.setGeometry(QtCore.QRect(18, 110, 103, 124)) 42 | self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") 43 | self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) 44 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 45 | self.verticalLayout.setObjectName("verticalLayout") 46 | self.checkbox_withdraw = QtWidgets.QCheckBox(self.verticalLayoutWidget) 47 | self.checkbox_withdraw.setChecked(False) 48 | self.checkbox_withdraw.setObjectName("checkbox_withdraw") 49 | self.verticalLayout.addWidget(self.checkbox_withdraw) 50 | self.formLayout = QtWidgets.QFormLayout() 51 | self.formLayout.setObjectName("formLayout") 52 | self.label_11 = QtWidgets.QLabel(self.verticalLayoutWidget) 53 | self.label_11.setObjectName("label_11") 54 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_11) 55 | self.label_7 = QtWidgets.QLabel(self.verticalLayoutWidget) 56 | self.label_7.setObjectName("label_7") 57 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_7) 58 | self.need_fwf = QtWidgets.QLineEdit(self.verticalLayoutWidget) 59 | self.need_fwf.setObjectName("need_fwf") 60 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.need_fwf) 61 | self.label_8 = QtWidgets.QLabel(self.verticalLayoutWidget) 62 | self.label_8.setObjectName("label_8") 63 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_8) 64 | self.need_fwg = QtWidgets.QLineEdit(self.verticalLayoutWidget) 65 | self.need_fwg.setObjectName("need_fwg") 66 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.need_fwg) 67 | self.label_9 = QtWidgets.QLabel(self.verticalLayoutWidget) 68 | self.label_9.setObjectName("label_9") 69 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_9) 70 | self.withdraw_min = QtWidgets.QLineEdit(self.verticalLayoutWidget) 71 | self.withdraw_min.setObjectName("withdraw_min") 72 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.withdraw_min) 73 | self.need_fww = QtWidgets.QLineEdit(self.verticalLayoutWidget) 74 | self.need_fww.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone) 75 | self.need_fww.setObjectName("need_fww") 76 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.need_fww) 77 | self.verticalLayout.addLayout(self.formLayout) 78 | self.verticalLayoutWidget_2 = QtWidgets.QWidget(Dialog) 79 | self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(142, 110, 187, 111)) 80 | self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2") 81 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2) 82 | self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) 83 | self.verticalLayout_3.setObjectName("verticalLayout_3") 84 | self.checkbox_auto_deposit = QtWidgets.QCheckBox(self.verticalLayoutWidget_2) 85 | self.checkbox_auto_deposit.setChecked(False) 86 | self.checkbox_auto_deposit.setObjectName("checkbox_auto_deposit") 87 | self.verticalLayout_3.addWidget(self.checkbox_auto_deposit) 88 | self.label_4 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 89 | font = QtGui.QFont() 90 | font.setPointSize(8) 91 | self.label_4.setFont(font) 92 | self.label_4.setAutoFillBackground(False) 93 | self.label_4.setStyleSheet("") 94 | self.label_4.setObjectName("label_4") 95 | self.verticalLayout_3.addWidget(self.label_4) 96 | self.gridLayout = QtWidgets.QGridLayout() 97 | self.gridLayout.setObjectName("gridLayout") 98 | self.fww_min = QtWidgets.QLineEdit(self.verticalLayoutWidget_2) 99 | self.fww_min.setObjectName("fww_min") 100 | self.gridLayout.addWidget(self.fww_min, 0, 1, 1, 1) 101 | self.label_13 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 102 | self.label_13.setObjectName("label_13") 103 | self.gridLayout.addWidget(self.label_13, 0, 2, 1, 1) 104 | self.label_15 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 105 | self.label_15.setObjectName("label_15") 106 | self.gridLayout.addWidget(self.label_15, 0, 4, 1, 1) 107 | self.fwf_min = QtWidgets.QLineEdit(self.verticalLayoutWidget_2) 108 | self.fwf_min.setObjectName("fwf_min") 109 | self.gridLayout.addWidget(self.fwf_min, 0, 3, 1, 1) 110 | self.fwg_min = QtWidgets.QLineEdit(self.verticalLayoutWidget_2) 111 | self.fwg_min.setObjectName("fwg_min") 112 | self.gridLayout.addWidget(self.fwg_min, 0, 5, 1, 1) 113 | self.label_17 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 114 | self.label_17.setObjectName("label_17") 115 | self.gridLayout.addWidget(self.label_17, 0, 0, 1, 1) 116 | self.verticalLayout_3.addLayout(self.gridLayout) 117 | self.label_5 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 118 | font = QtGui.QFont() 119 | font.setPointSize(8) 120 | self.label_5.setFont(font) 121 | self.label_5.setObjectName("label_5") 122 | self.verticalLayout_3.addWidget(self.label_5) 123 | self.gridLayout_2 = QtWidgets.QGridLayout() 124 | self.gridLayout_2.setObjectName("gridLayout_2") 125 | self.deposit_fww = QtWidgets.QLineEdit(self.verticalLayoutWidget_2) 126 | self.deposit_fww.setObjectName("deposit_fww") 127 | self.gridLayout_2.addWidget(self.deposit_fww, 0, 1, 1, 1) 128 | self.label_18 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 129 | self.label_18.setObjectName("label_18") 130 | self.gridLayout_2.addWidget(self.label_18, 0, 0, 1, 1) 131 | self.deposit_fwg = QtWidgets.QLineEdit(self.verticalLayoutWidget_2) 132 | self.deposit_fwg.setObjectName("deposit_fwg") 133 | self.gridLayout_2.addWidget(self.deposit_fwg, 0, 5, 1, 1) 134 | self.label_19 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 135 | self.label_19.setObjectName("label_19") 136 | self.gridLayout_2.addWidget(self.label_19, 0, 4, 1, 1) 137 | self.deposit_fwf = QtWidgets.QLineEdit(self.verticalLayoutWidget_2) 138 | self.deposit_fwf.setObjectName("deposit_fwf") 139 | self.gridLayout_2.addWidget(self.deposit_fwf, 0, 3, 1, 1) 140 | self.label_20 = QtWidgets.QLabel(self.verticalLayoutWidget_2) 141 | self.label_20.setObjectName("label_20") 142 | self.gridLayout_2.addWidget(self.label_20, 0, 2, 1, 1) 143 | self.verticalLayout_3.addLayout(self.gridLayout_2) 144 | self.line = QtWidgets.QFrame(Dialog) 145 | self.line.setGeometry(QtCore.QRect(-44, 90, 0, 20)) 146 | self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) 147 | self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) 148 | self.line.setObjectName("line") 149 | self.gridLayoutWidget = QtWidgets.QWidget(Dialog) 150 | self.gridLayoutWidget.setGeometry(QtCore.QRect(348, 110, 171, 111)) 151 | self.gridLayoutWidget.setObjectName("gridLayoutWidget") 152 | self.gridLayout_3 = QtWidgets.QGridLayout(self.gridLayoutWidget) 153 | self.gridLayout_3.setContentsMargins(0, 0, 0, 0) 154 | self.gridLayout_3.setObjectName("gridLayout_3") 155 | self.checkbox_sell_corn = QtWidgets.QCheckBox(self.gridLayoutWidget) 156 | self.checkbox_sell_corn.setChecked(False) 157 | self.checkbox_sell_corn.setObjectName("checkbox_sell_corn") 158 | self.gridLayout_3.addWidget(self.checkbox_sell_corn, 0, 0, 1, 1) 159 | self.remaining_corn_num = QtWidgets.QLineEdit(self.gridLayoutWidget) 160 | self.remaining_corn_num.setObjectName("remaining_corn_num") 161 | self.gridLayout_3.addWidget(self.remaining_corn_num, 0, 2, 1, 1) 162 | self.label_10 = QtWidgets.QLabel(self.gridLayoutWidget) 163 | self.label_10.setObjectName("label_10") 164 | self.gridLayout_3.addWidget(self.label_10, 0, 1, 1, 1) 165 | self.checkbox_sell_barley = QtWidgets.QCheckBox(self.gridLayoutWidget) 166 | self.checkbox_sell_barley.setChecked(False) 167 | self.checkbox_sell_barley.setObjectName("checkbox_sell_barley") 168 | self.gridLayout_3.addWidget(self.checkbox_sell_barley, 1, 0, 1, 1) 169 | self.checkbox_sell_milk = QtWidgets.QCheckBox(self.gridLayoutWidget) 170 | self.checkbox_sell_milk.setChecked(False) 171 | self.checkbox_sell_milk.setObjectName("checkbox_sell_milk") 172 | self.gridLayout_3.addWidget(self.checkbox_sell_milk, 2, 0, 1, 1) 173 | self.checkbox_sell_egg = QtWidgets.QCheckBox(self.gridLayoutWidget) 174 | self.checkbox_sell_egg.setChecked(False) 175 | self.checkbox_sell_egg.setObjectName("checkbox_sell_egg") 176 | self.gridLayout_3.addWidget(self.checkbox_sell_egg, 3, 0, 1, 1) 177 | self.label_12 = QtWidgets.QLabel(self.gridLayoutWidget) 178 | self.label_12.setObjectName("label_12") 179 | self.gridLayout_3.addWidget(self.label_12, 1, 1, 1, 1) 180 | self.label_14 = QtWidgets.QLabel(self.gridLayoutWidget) 181 | self.label_14.setObjectName("label_14") 182 | self.gridLayout_3.addWidget(self.label_14, 2, 1, 1, 1) 183 | self.label_16 = QtWidgets.QLabel(self.gridLayoutWidget) 184 | self.label_16.setObjectName("label_16") 185 | self.gridLayout_3.addWidget(self.label_16, 3, 1, 1, 1) 186 | self.remaining_barley_num = QtWidgets.QLineEdit(self.gridLayoutWidget) 187 | self.remaining_barley_num.setObjectName("remaining_barley_num") 188 | self.gridLayout_3.addWidget(self.remaining_barley_num, 1, 2, 1, 1) 189 | self.remaining_milk_num = QtWidgets.QLineEdit(self.gridLayoutWidget) 190 | self.remaining_milk_num.setObjectName("remaining_milk_num") 191 | self.gridLayout_3.addWidget(self.remaining_milk_num, 2, 2, 1, 1) 192 | self.remaining_egg_num = QtWidgets.QLineEdit(self.gridLayoutWidget) 193 | self.remaining_egg_num.setObjectName("remaining_egg_num") 194 | self.gridLayout_3.addWidget(self.remaining_egg_num, 3, 2, 1, 1) 195 | self.gridLayoutWidget_2 = QtWidgets.QWidget(Dialog) 196 | self.gridLayoutWidget_2.setGeometry(QtCore.QRect(490, 10, 131, 74)) 197 | self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2") 198 | self.gridLayout_4 = QtWidgets.QGridLayout(self.gridLayoutWidget_2) 199 | self.gridLayout_4.setContentsMargins(0, 0, 0, 0) 200 | self.gridLayout_4.setObjectName("gridLayout_4") 201 | self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget_2) 202 | self.label_2.setObjectName("label_2") 203 | self.gridLayout_4.addWidget(self.label_2, 0, 0, 1, 1, QtCore.Qt.AlignmentFlag.AlignRight) 204 | self.spinbox_min_durability = QtWidgets.QSpinBox(self.gridLayoutWidget_2) 205 | self.spinbox_min_durability.setKeyboardTracking(True) 206 | self.spinbox_min_durability.setSuffix("") 207 | self.spinbox_min_durability.setMaximum(2000) 208 | self.spinbox_min_durability.setSingleStep(10) 209 | self.spinbox_min_durability.setProperty("value", 20) 210 | self.spinbox_min_durability.setObjectName("spinbox_min_durability") 211 | self.gridLayout_4.addWidget(self.spinbox_min_durability, 2, 1, 1, 1) 212 | self.spinbox_energy = QtWidgets.QSpinBox(self.gridLayoutWidget_2) 213 | self.spinbox_energy.setMaximum(9000) 214 | self.spinbox_energy.setSingleStep(100) 215 | self.spinbox_energy.setProperty("value", 500) 216 | self.spinbox_energy.setObjectName("spinbox_energy") 217 | self.gridLayout_4.addWidget(self.spinbox_energy, 0, 1, 1, 1) 218 | self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget_2) 219 | self.label_3.setObjectName("label_3") 220 | self.gridLayout_4.addWidget(self.label_3, 1, 0, 1, 1, QtCore.Qt.AlignmentFlag.AlignRight) 221 | self.label_6 = QtWidgets.QLabel(self.gridLayoutWidget_2) 222 | self.label_6.setObjectName("label_6") 223 | self.gridLayout_4.addWidget(self.label_6, 2, 0, 1, 1) 224 | self.spinbox_min_energy = QtWidgets.QSpinBox(self.gridLayoutWidget_2) 225 | self.spinbox_min_energy.setEnabled(True) 226 | self.spinbox_min_energy.setMaximum(9000) 227 | self.spinbox_min_energy.setSingleStep(10) 228 | self.spinbox_min_energy.setProperty("value", 50) 229 | self.spinbox_min_energy.setObjectName("spinbox_min_energy") 230 | self.gridLayout_4.addWidget(self.spinbox_min_energy, 1, 1, 1, 1) 231 | self.verticalLayoutWidget_3 = QtWidgets.QWidget(Dialog) 232 | self.verticalLayoutWidget_3.setGeometry(QtCore.QRect(538, 110, 111, 116)) 233 | self.verticalLayoutWidget_3.setObjectName("verticalLayoutWidget_3") 234 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_3) 235 | self.verticalLayout_4.setContentsMargins(0, 0, 0, 0) 236 | self.verticalLayout_4.setObjectName("verticalLayout_4") 237 | self.checkbox_auto_plant = QtWidgets.QCheckBox(self.verticalLayoutWidget_3) 238 | self.checkbox_auto_plant.setChecked(False) 239 | self.checkbox_auto_plant.setObjectName("checkbox_auto_plant") 240 | self.verticalLayout_4.addWidget(self.checkbox_auto_plant) 241 | self.formLayout_4 = QtWidgets.QFormLayout() 242 | self.formLayout_4.setObjectName("formLayout_4") 243 | self.label_21 = QtWidgets.QLabel(self.verticalLayoutWidget_3) 244 | self.label_21.setObjectName("label_21") 245 | self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_21) 246 | self.label_22 = QtWidgets.QLabel(self.verticalLayoutWidget_3) 247 | self.label_22.setObjectName("label_22") 248 | self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_22) 249 | self.cornseed_num = QtWidgets.QLineEdit(self.verticalLayoutWidget_3) 250 | self.cornseed_num.setObjectName("cornseed_num") 251 | self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.cornseed_num) 252 | self.barleyseed_num = QtWidgets.QLineEdit(self.verticalLayoutWidget_3) 253 | self.barleyseed_num.setObjectName("barleyseed_num") 254 | self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.barleyseed_num) 255 | self.verticalLayout_4.addLayout(self.formLayout_4) 256 | self.checkbox_buy_barley_seed = QtWidgets.QCheckBox(self.verticalLayoutWidget_3) 257 | self.checkbox_buy_barley_seed.setChecked(False) 258 | self.checkbox_buy_barley_seed.setObjectName("checkbox_buy_barley_seed") 259 | self.verticalLayout_4.addWidget(self.checkbox_buy_barley_seed) 260 | self.checkbox_buy_corn_seed = QtWidgets.QCheckBox(self.verticalLayoutWidget_3) 261 | self.checkbox_buy_corn_seed.setChecked(False) 262 | self.checkbox_buy_corn_seed.setObjectName("checkbox_buy_corn_seed") 263 | self.verticalLayout_4.addWidget(self.checkbox_buy_corn_seed) 264 | self.line_3 = QtWidgets.QFrame(Dialog) 265 | self.line_3.setGeometry(QtCore.QRect(123, 110, 16, 111)) 266 | self.line_3.setFrameShape(QtWidgets.QFrame.Shape.VLine) 267 | self.line_3.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) 268 | self.line_3.setObjectName("line_3") 269 | self.line_4 = QtWidgets.QFrame(Dialog) 270 | self.line_4.setGeometry(QtCore.QRect(330, 110, 16, 111)) 271 | self.line_4.setFrameShape(QtWidgets.QFrame.Shape.VLine) 272 | self.line_4.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) 273 | self.line_4.setObjectName("line_4") 274 | self.line_5 = QtWidgets.QFrame(Dialog) 275 | self.line_5.setGeometry(QtCore.QRect(520, 110, 16, 111)) 276 | self.line_5.setFrameShape(QtWidgets.QFrame.Shape.VLine) 277 | self.line_5.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) 278 | self.line_5.setObjectName("line_5") 279 | self.label_24 = QtWidgets.QLabel(Dialog) 280 | self.label_24.setGeometry(QtCore.QRect(20, 490, 561, 16)) 281 | self.label_24.setObjectName("label_24") 282 | self.line_6 = QtWidgets.QFrame(Dialog) 283 | self.line_6.setGeometry(QtCore.QRect(0, 92, 821, 16)) 284 | self.line_6.setFrameShape(QtWidgets.QFrame.Shape.HLine) 285 | self.line_6.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) 286 | self.line_6.setObjectName("line_6") 287 | self.verticalLayoutWidget_4 = QtWidgets.QWidget(Dialog) 288 | self.verticalLayoutWidget_4.setGeometry(QtCore.QRect(669, 110, 85, 62)) 289 | self.verticalLayoutWidget_4.setObjectName("verticalLayoutWidget_4") 290 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_4) 291 | self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) 292 | self.verticalLayout_2.setObjectName("verticalLayout_2") 293 | self.checkbox_buy_food = QtWidgets.QCheckBox(self.verticalLayoutWidget_4) 294 | self.checkbox_buy_food.setChecked(False) 295 | self.checkbox_buy_food.setObjectName("checkbox_buy_food") 296 | self.verticalLayout_2.addWidget(self.checkbox_buy_food) 297 | self.label_23 = QtWidgets.QLabel(self.verticalLayoutWidget_4) 298 | self.label_23.setObjectName("label_23") 299 | self.verticalLayout_2.addWidget(self.label_23) 300 | self.buy_food_num = QtWidgets.QLineEdit(self.verticalLayoutWidget_4) 301 | self.buy_food_num.setObjectName("buy_food_num") 302 | self.verticalLayout_2.addWidget(self.buy_food_num) 303 | self.line_7 = QtWidgets.QFrame(Dialog) 304 | self.line_7.setGeometry(QtCore.QRect(650, 110, 16, 111)) 305 | self.line_7.setFrameShape(QtWidgets.QFrame.Shape.VLine) 306 | self.line_7.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) 307 | self.line_7.setObjectName("line_7") 308 | self.splitter = QtWidgets.QSplitter(Dialog) 309 | self.splitter.setGeometry(QtCore.QRect(20, 73, 461, 16)) 310 | self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal) 311 | self.splitter.setObjectName("splitter") 312 | self.checkbox_build = QtWidgets.QCheckBox(self.splitter) 313 | self.checkbox_build.setChecked(False) 314 | self.checkbox_build.setObjectName("checkbox_build") 315 | self.checkbox_mining = QtWidgets.QCheckBox(self.splitter) 316 | self.checkbox_mining.setEnabled(True) 317 | self.checkbox_mining.setChecked(True) 318 | self.checkbox_mining.setObjectName("checkbox_mining") 319 | self.checkbox_chicken = QtWidgets.QCheckBox(self.splitter) 320 | self.checkbox_chicken.setChecked(False) 321 | self.checkbox_chicken.setObjectName("checkbox_chicken") 322 | self.checkbox_plant = QtWidgets.QCheckBox(self.splitter) 323 | self.checkbox_plant.setChecked(False) 324 | self.checkbox_plant.setObjectName("checkbox_plant") 325 | self.checkbox_cow = QtWidgets.QCheckBox(self.splitter) 326 | self.checkbox_cow.setChecked(False) 327 | self.checkbox_cow.setObjectName("checkbox_cow") 328 | self.checkbox_breeding = QtWidgets.QCheckBox(self.splitter) 329 | self.checkbox_breeding.setChecked(False) 330 | self.checkbox_breeding.setObjectName("checkbox_breeding") 331 | self.checkbox_mbs = QtWidgets.QCheckBox(self.splitter) 332 | self.checkbox_mbs.setChecked(False) 333 | self.checkbox_mbs.setObjectName("checkbox_mbs") 334 | self.checkbox_mbs_mint = QtWidgets.QCheckBox(self.splitter) 335 | self.checkbox_mbs_mint.setChecked(False) 336 | self.checkbox_mbs_mint.setObjectName("checkbox_mbs_mint") 337 | self.label_25 = QtWidgets.QLabel(Dialog) 338 | self.label_25.setGeometry(QtCore.QRect(20, 42, 41, 16)) 339 | self.label_25.setObjectName("label_25") 340 | self.comboBox_rpc_domain = QtWidgets.QComboBox(Dialog) 341 | self.comboBox_rpc_domain.setGeometry(QtCore.QRect(50, 40, 201, 22)) 342 | self.comboBox_rpc_domain.setObjectName("comboBox_rpc_domain") 343 | self.comboBox_rpc_domain.addItem("") 344 | self.comboBox_rpc_domain.addItem("") 345 | self.comboBox_rpc_domain.addItem("") 346 | self.comboBox_rpc_domain.addItem("") 347 | self.comboBox_rpc_domain.addItem("") 348 | self.comboBox_rpc_domain.addItem("") 349 | self.comboBox_rpc_domain.addItem("") 350 | self.comboBox_rpc_domain.addItem("") 351 | self.label_26 = QtWidgets.QLabel(Dialog) 352 | self.label_26.setGeometry(QtCore.QRect(260, 42, 51, 16)) 353 | self.label_26.setObjectName("label_26") 354 | self.comboBox_assets_domain = QtWidgets.QComboBox(Dialog) 355 | self.comboBox_assets_domain.setGeometry(QtCore.QRect(310, 40, 161, 22)) 356 | self.comboBox_assets_domain.setObjectName("comboBox_assets_domain") 357 | self.comboBox_assets_domain.addItem("") 358 | self.comboBox_assets_domain.addItem("") 359 | 360 | self.retranslateUi(Dialog) 361 | QtCore.QMetaObject.connectSlotsByName(Dialog) 362 | 363 | def retranslateUi(self, Dialog): 364 | _translate = QtCore.QCoreApplication.translate 365 | Dialog.setWindowTitle(_translate("Dialog", "农民世界助手")) 366 | self.label_1.setText(_translate("Dialog", "账号:")) 367 | self.edit_account.setText(_translate("Dialog", "abcde.wam")) 368 | self.button_start.setText(_translate("Dialog", "启动")) 369 | self.edit_proxy.setText(_translate("Dialog", "127.0.0.1:1080")) 370 | self.checkbox_proxy.setText(_translate("Dialog", "启用代理")) 371 | self.checkbox_withdraw.setText(_translate("Dialog", "5%自动提现")) 372 | self.label_11.setText(_translate("Dialog", "保留木头")) 373 | self.label_7.setText(_translate("Dialog", "保留食物")) 374 | self.need_fwf.setText(_translate("Dialog", "1000")) 375 | self.label_8.setText(_translate("Dialog", "保留金子")) 376 | self.need_fwg.setText(_translate("Dialog", "1000")) 377 | self.label_9.setToolTip(_translate("Dialog", "最少提现数量,3种材料总和")) 378 | self.label_9.setText(_translate("Dialog", "最少提现?")) 379 | self.withdraw_min.setText(_translate("Dialog", "500")) 380 | self.need_fww.setText(_translate("Dialog", "0")) 381 | self.checkbox_auto_deposit.setText(_translate("Dialog", "自动充值资源")) 382 | self.label_4.setText(_translate("Dialog", "触发充值:")) 383 | self.fww_min.setText(_translate("Dialog", "0")) 384 | self.label_13.setText(_translate("Dialog", "肉")) 385 | self.label_15.setText(_translate("Dialog", "金")) 386 | self.fwf_min.setText(_translate("Dialog", "200")) 387 | self.fwg_min.setText(_translate("Dialog", "200")) 388 | self.label_17.setText(_translate("Dialog", "木")) 389 | self.label_5.setText(_translate("Dialog", "充值数量:")) 390 | self.deposit_fww.setText(_translate("Dialog", "0")) 391 | self.label_18.setText(_translate("Dialog", "木")) 392 | self.deposit_fwg.setText(_translate("Dialog", "200")) 393 | self.label_19.setText(_translate("Dialog", "金")) 394 | self.deposit_fwf.setText(_translate("Dialog", "200")) 395 | self.label_20.setText(_translate("Dialog", "肉")) 396 | self.checkbox_sell_corn.setText(_translate("Dialog", "卖玉米")) 397 | self.remaining_corn_num.setText(_translate("Dialog", "0")) 398 | self.label_10.setText(_translate("Dialog", "保留玉米")) 399 | self.checkbox_sell_barley.setText(_translate("Dialog", "卖大麦")) 400 | self.checkbox_sell_milk.setText(_translate("Dialog", "卖牛奶")) 401 | self.checkbox_sell_egg.setText(_translate("Dialog", "卖鸡蛋")) 402 | self.label_12.setText(_translate("Dialog", "保留大麦")) 403 | self.label_14.setText(_translate("Dialog", "保留牛奶")) 404 | self.label_16.setText(_translate("Dialog", "保留鸡蛋")) 405 | self.remaining_barley_num.setText(_translate("Dialog", "0")) 406 | self.remaining_milk_num.setText(_translate("Dialog", "0")) 407 | self.remaining_egg_num.setText(_translate("Dialog", "0")) 408 | self.label_2.setText(_translate("Dialog", "能量恢复")) 409 | self.label_3.setText(_translate("Dialog", "最低能量")) 410 | self.label_6.setText(_translate("Dialog", "最低耐久度%")) 411 | self.checkbox_auto_plant.setText(_translate("Dialog", "自动播种")) 412 | self.label_21.setText(_translate("Dialog", "大麦种子")) 413 | self.label_22.setText(_translate("Dialog", "玉米种子")) 414 | self.cornseed_num.setText(_translate("Dialog", "0")) 415 | self.barleyseed_num.setText(_translate("Dialog", "8")) 416 | self.checkbox_buy_barley_seed.setText(_translate("Dialog", "自动买大麦种子")) 417 | self.checkbox_buy_corn_seed.setText(_translate("Dialog", "自动买玉米种子")) 418 | self.label_24.setText(_translate("Dialog", "如果程序对你有帮助,欢迎打赏,支持我继续不断完善这个项目。感谢! WAX钱包地址:openfarmercn ")) 419 | self.checkbox_buy_food.setToolTip(_translate("Dialog", "自动买食物[玉米或大麦](喂动物食物不够时,触发购买)")) 420 | self.checkbox_buy_food.setText(_translate("Dialog", "自动买食物")) 421 | self.label_23.setToolTip(_translate("Dialog", "一次购买的数量")) 422 | self.label_23.setText(_translate("Dialog", "购买数量:")) 423 | self.buy_food_num.setText(_translate("Dialog", "0")) 424 | self.checkbox_build.setText(_translate("Dialog", "建造")) 425 | self.checkbox_mining.setText(_translate("Dialog", "挖矿")) 426 | self.checkbox_chicken.setText(_translate("Dialog", "养鸡")) 427 | self.checkbox_plant.setText(_translate("Dialog", "作物浇水")) 428 | self.checkbox_cow.setText(_translate("Dialog", "养牛")) 429 | self.checkbox_breeding.setText(_translate("Dialog", "繁殖")) 430 | self.checkbox_mbs.setText(_translate("Dialog", "会员")) 431 | self.checkbox_mbs_mint.setText(_translate("Dialog", "会员存储")) 432 | self.label_25.setText(_translate("Dialog", "节点")) 433 | self.comboBox_rpc_domain.setItemText(0, _translate("Dialog", "https://api.wax.alohaeos.com")) 434 | self.comboBox_rpc_domain.setItemText(1, _translate("Dialog", "https://wax.dapplica.io")) 435 | self.comboBox_rpc_domain.setItemText(2, _translate("Dialog", "https://wax.pink.gg")) 436 | self.comboBox_rpc_domain.setItemText(3, _translate("Dialog", "https://api.waxsweden.org")) 437 | self.comboBox_rpc_domain.setItemText(4, _translate("Dialog", "https://wax.dapplica.io")) 438 | self.comboBox_rpc_domain.setItemText(5, _translate("Dialog", "https://wax.eosphere.io")) 439 | self.comboBox_rpc_domain.setItemText(6, _translate("Dialog", "https://api.wax.greeneosio.com")) 440 | self.comboBox_rpc_domain.setItemText(7, _translate("Dialog", "https://wax.cryptolions.io")) 441 | self.label_26.setText(_translate("Dialog", "原子节点")) 442 | self.comboBox_assets_domain.setItemText(0, _translate("Dialog", "https://wax.api.atomicassets.io")) 443 | self.comboBox_assets_domain.setItemText(1, _translate("Dialog", "https://atomic.wax.eosrio.io")) 444 | -------------------------------------------------------------------------------- /dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 775 10 | 517 11 | 12 | 13 | 14 | 农民世界助手 15 | 16 | 17 | 18 | C:/Users/Administrator/.designer/backup/favicon.icoC:/Users/Administrator/.designer/backup/favicon.ico 19 | 20 | 21 | 22 | 23 | 20 24 | 17 25 | 41 26 | 16 27 | 28 | 29 | 30 | 账号: 31 | 32 | 33 | 34 | 35 | 36 | 50 37 | 15 38 | 116 39 | 20 40 | 41 | 42 | 43 | abcde.wam 44 | 45 | 46 | 47 | 48 | 49 | 20 50 | 260 51 | 741 52 | 221 53 | 54 | 55 | 56 | true 57 | 58 | 59 | 100 60 | 61 | 62 | 63 | 64 | 65 | 650 66 | 30 67 | 101 68 | 41 69 | 70 | 71 | 72 | 启动 73 | 74 | 75 | 76 | 77 | 78 | 265 79 | 15 80 | 101 81 | 20 82 | 83 | 84 | 85 | 127.0.0.1:1080 86 | 87 | 88 | 89 | 90 | 91 | 190 92 | 15 93 | 71 94 | 20 95 | 96 | 97 | 98 | 启用代理 99 | 100 | 101 | false 102 | 103 | 104 | 105 | 106 | 107 | 18 108 | 110 109 | 103 110 | 124 111 | 112 | 113 | 114 | 115 | 116 | 117 | 5%自动提现 118 | 119 | 120 | false 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 保留木头 130 | 131 | 132 | 133 | 134 | 135 | 136 | 保留食物 137 | 138 | 139 | 140 | 141 | 142 | 143 | 1000 144 | 145 | 146 | 147 | 148 | 149 | 150 | 保留金子 151 | 152 | 153 | 154 | 155 | 156 | 157 | 1000 158 | 159 | 160 | 161 | 162 | 163 | 164 | 最少提现数量,3种材料总和 165 | 166 | 167 | 最少提现? 168 | 169 | 170 | 171 | 172 | 173 | 174 | 500 175 | 176 | 177 | 178 | 179 | 180 | 181 | Qt::ImhNone 182 | 183 | 184 | 0 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 142 196 | 110 197 | 187 198 | 111 199 | 200 | 201 | 202 | 203 | 204 | 205 | 自动充值资源 206 | 207 | 208 | false 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 8 217 | 218 | 219 | 220 | false 221 | 222 | 223 | 224 | 225 | 226 | 触发充值: 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 0 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 200 257 | 258 | 259 | 260 | 261 | 262 | 263 | 200 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 8 281 | 282 | 283 | 284 | 充值数量: 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 0 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 200 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 200 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | -44 340 | 90 341 | 0 342 | 20 343 | 344 | 345 | 346 | Qt::Horizontal 347 | 348 | 349 | 350 | 351 | 352 | 348 353 | 110 354 | 171 355 | 111 356 | 357 | 358 | 359 | 360 | 361 | 362 | 卖玉米 363 | 364 | 365 | false 366 | 367 | 368 | 369 | 370 | 371 | 372 | 0 373 | 374 | 375 | 376 | 377 | 378 | 379 | 保留玉米 380 | 381 | 382 | 383 | 384 | 385 | 386 | 卖大麦 387 | 388 | 389 | false 390 | 391 | 392 | 393 | 394 | 395 | 396 | 卖牛奶 397 | 398 | 399 | false 400 | 401 | 402 | 403 | 404 | 405 | 406 | 卖鸡蛋 407 | 408 | 409 | false 410 | 411 | 412 | 413 | 414 | 415 | 416 | 保留大麦 417 | 418 | 419 | 420 | 421 | 422 | 423 | 保留牛奶 424 | 425 | 426 | 427 | 428 | 429 | 430 | 保留鸡蛋 431 | 432 | 433 | 434 | 435 | 436 | 437 | 0 438 | 439 | 440 | 441 | 442 | 443 | 444 | 0 445 | 446 | 447 | 448 | 449 | 450 | 451 | 0 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 490 461 | 10 462 | 131 463 | 74 464 | 465 | 466 | 467 | 468 | 469 | 470 | 能量恢复 471 | 472 | 473 | 474 | 475 | 476 | 477 | true 478 | 479 | 480 | 481 | 482 | 483 | 2000 484 | 485 | 486 | 10 487 | 488 | 489 | 20 490 | 491 | 492 | 493 | 494 | 495 | 496 | 9000 497 | 498 | 499 | 100 500 | 501 | 502 | 500 503 | 504 | 505 | 506 | 507 | 508 | 509 | 最低能量 510 | 511 | 512 | 513 | 514 | 515 | 516 | 最低耐久度% 517 | 518 | 519 | 520 | 521 | 522 | 523 | true 524 | 525 | 526 | 9000 527 | 528 | 529 | 10 530 | 531 | 532 | 50 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 538 542 | 110 543 | 111 544 | 116 545 | 546 | 547 | 548 | 549 | 550 | 551 | 自动播种 552 | 553 | 554 | false 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 大麦种子 564 | 565 | 566 | 567 | 568 | 569 | 570 | 玉米种子 571 | 572 | 573 | 574 | 575 | 576 | 577 | 0 578 | 579 | 580 | 581 | 582 | 583 | 584 | 8 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 自动买大麦种子 594 | 595 | 596 | false 597 | 598 | 599 | 600 | 601 | 602 | 603 | 自动买玉米种子 604 | 605 | 606 | false 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 123 616 | 110 617 | 16 618 | 111 619 | 620 | 621 | 622 | Qt::Vertical 623 | 624 | 625 | 626 | 627 | 628 | 330 629 | 110 630 | 16 631 | 111 632 | 633 | 634 | 635 | Qt::Vertical 636 | 637 | 638 | 639 | 640 | 641 | 520 642 | 110 643 | 16 644 | 111 645 | 646 | 647 | 648 | Qt::Vertical 649 | 650 | 651 | 652 | 653 | 654 | 20 655 | 490 656 | 561 657 | 16 658 | 659 | 660 | 661 | 如果程序对你有帮助,欢迎打赏,支持我继续不断完善这个项目。感谢! WAX钱包地址:openfarmercn 662 | 663 | 664 | 665 | 666 | 667 | 0 668 | 92 669 | 821 670 | 16 671 | 672 | 673 | 674 | Qt::Horizontal 675 | 676 | 677 | 678 | 679 | 680 | 669 681 | 110 682 | 85 683 | 62 684 | 685 | 686 | 687 | 688 | 689 | 690 | 自动买食物[玉米或大麦](喂动物食物不够时,触发购买) 691 | 692 | 693 | 自动买食物 694 | 695 | 696 | false 697 | 698 | 699 | 700 | 701 | 702 | 703 | 一次购买的数量 704 | 705 | 706 | 购买数量: 707 | 708 | 709 | 710 | 711 | 712 | 713 | 0 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 650 723 | 110 724 | 16 725 | 111 726 | 727 | 728 | 729 | Qt::Vertical 730 | 731 | 732 | 733 | 734 | 735 | 20 736 | 73 737 | 461 738 | 16 739 | 740 | 741 | 742 | Qt::Horizontal 743 | 744 | 745 | 746 | 建造 747 | 748 | 749 | false 750 | 751 | 752 | 753 | 754 | true 755 | 756 | 757 | 挖矿 758 | 759 | 760 | true 761 | 762 | 763 | 764 | 765 | 养鸡 766 | 767 | 768 | false 769 | 770 | 771 | 772 | 773 | 作物浇水 774 | 775 | 776 | false 777 | 778 | 779 | 780 | 781 | 养牛 782 | 783 | 784 | false 785 | 786 | 787 | 788 | 789 | 繁殖 790 | 791 | 792 | false 793 | 794 | 795 | 796 | 797 | 会员 798 | 799 | 800 | false 801 | 802 | 803 | 804 | 805 | 会员存储 806 | 807 | 808 | false 809 | 810 | 811 | 812 | 813 | 814 | 815 | 20 816 | 42 817 | 41 818 | 16 819 | 820 | 821 | 822 | 节点 823 | 824 | 825 | 826 | 827 | 828 | 50 829 | 40 830 | 201 831 | 22 832 | 833 | 834 | 835 | 836 | https://api.wax.alohaeos.com 837 | 838 | 839 | 840 | 841 | https://wax.dapplica.io 842 | 843 | 844 | 845 | 846 | https://wax.pink.gg 847 | 848 | 849 | 850 | 851 | https://api.waxsweden.org 852 | 853 | 854 | 855 | 856 | https://wax.dapplica.io 857 | 858 | 859 | 860 | 861 | https://wax.eosphere.io 862 | 863 | 864 | 865 | 866 | https://api.wax.greeneosio.com 867 | 868 | 869 | 870 | 871 | https://wax.cryptolions.io 872 | 873 | 874 | 875 | 876 | 877 | 878 | 260 879 | 42 880 | 51 881 | 16 882 | 883 | 884 | 885 | 原子节点 886 | 887 | 888 | 889 | 890 | 891 | 310 892 | 40 893 | 161 894 | 22 895 | 896 | 897 | 898 | 899 | https://wax.api.atomicassets.io 900 | 901 | 902 | 903 | 904 | https://atomic.wax.eosrio.io 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | -------------------------------------------------------------------------------- /doc/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encoderlee/OpenFarmer/e8dd3e7bb6eba90a969208aef5ee05237898a92c/doc/demo1.png -------------------------------------------------------------------------------- /doc/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encoderlee/OpenFarmer/e8dd3e7bb6eba90a969208aef5ee05237898a92c/doc/demo2.png -------------------------------------------------------------------------------- /doc/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encoderlee/OpenFarmer/e8dd3e7bb6eba90a969208aef5ee05237898a92c/doc/demo3.png -------------------------------------------------------------------------------- /doc/question1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encoderlee/OpenFarmer/e8dd3e7bb6eba90a969208aef5ee05237898a92c/doc/question1.png -------------------------------------------------------------------------------- /farmer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import random 3 | import time 4 | from selenium import webdriver 5 | from selenium.webdriver.common.by import By 6 | from selenium.webdriver.support.ui import WebDriverWait 7 | from selenium.webdriver.support import expected_conditions as EC 8 | from selenium.common.exceptions import WebDriverException 9 | import tenacity 10 | from tenacity import stop_after_attempt, wait_fixed, retry_if_exception_type, RetryCallState 11 | import logging 12 | import requests 13 | from requests.exceptions import RequestException 14 | import functools 15 | from decimal import Decimal 16 | from typing import List, Dict 17 | import base64 18 | from pprint import pprint 19 | import logger 20 | import utils 21 | from utils import plat 22 | from settings import user_param 23 | import res 24 | from res import Building, Resoure, Animal, Asset, Farming, Crop, NFT, Axe, Tool, Token, Chicken, FishingRod, MBS 25 | from res import BabyCalf, Calf, FeMaleCalf, MaleCalf, Bull, DairyCow, MbsSavedClaims 26 | 27 | from datetime import datetime, timedelta 28 | from settings import cfg 29 | import os 30 | from logger import log 31 | 32 | 33 | class FarmerException(Exception): 34 | pass 35 | 36 | 37 | class CookieExpireException(FarmerException): 38 | pass 39 | 40 | 41 | # 调用智能合约出错,此时应停止并检查日志,不宜反复重试 42 | class TransactException(FarmerException): 43 | # 有的智能合约错误可以重试,-1为无限重试 44 | def __init__(self, msg, retry=True, max_retry_times: int = -1): 45 | super().__init__(msg) 46 | self.retry = retry 47 | self.max_retry_times = max_retry_times 48 | 49 | 50 | # 遇到不可恢复的错误 ,终止程序 51 | class StopException(FarmerException): 52 | pass 53 | 54 | 55 | class Status: 56 | Continue = 1 57 | Stop = 2 58 | 59 | 60 | class Farmer: 61 | # wax rpc 62 | # url_rpc = "https://api.wax.alohaeos.com/v1/chain/" 63 | # url_rpc = "https://wax.dapplica.io/v1/chain/" 64 | # url_table_row = url_rpc + "get_table_rows" 65 | # 资产API 66 | # url_assets = "https://wax.api.atomicassets.io/atomicassets/v1/assets" 67 | # url_assets = "https://atomic.wax.eosrio.io/atomicassets/v1/assets" 68 | waxjs: str = None 69 | myjs: str = None 70 | chrome_data_dir = os.path.abspath(cfg.chrome_data_dir) 71 | 72 | def __init__(self): 73 | self.url_rpc: str = None 74 | self.url_table_row: str = None 75 | self.url_assets: str = None 76 | 77 | self.wax_account: str = None 78 | self.login_name: str = None 79 | self.password: str = None 80 | self.driver: webdriver.Chrome = None 81 | self.proxy: str = None 82 | self.http: requests.Session = None 83 | self.cookies: List[dict] = None 84 | self.log: logging.LoggerAdapter = log 85 | # 下一次可以操作东西的时间 86 | self.next_operate_time: datetime = datetime.max 87 | # 下一次扫描时间 88 | self.next_scan_time: datetime = datetime.min 89 | # 本轮扫描中暂不可操作的东西 90 | self.not_operational: List[Farming] = [] 91 | # 智能合约连续出错次数 92 | self.count_error_transact = 0 93 | # 本轮扫描中作物操作成功个数 94 | self.count_success_claim = 0 95 | # 本轮扫描中作物操作失败个数 96 | self.count_error_claim = 0 97 | # 本轮开始时的资源数量 98 | self.resoure: Resoure = None 99 | self.token: Token = None 100 | self.mbs_saved_claims: MbsSavedClaims = None 101 | 102 | def close(self): 103 | if self.driver: 104 | self.log.info("稍等,程序正在退出") 105 | self.driver.quit() 106 | 107 | def init(self): 108 | self.url_rpc = user_param.rpc_domain + '/v1/chain/' 109 | self.url_table_row = user_param.rpc_domain + '/v1/chain/get_table_rows' 110 | self.url_assets = user_param.assets_domain + '/atomicassets/v1/assets' 111 | 112 | self.log.extra["tag"] = self.wax_account 113 | options = webdriver.ChromeOptions() 114 | # options.add_argument("--headless") 115 | # options.add_argument("--no-sandbox") 116 | options.add_argument("--disable-extensions") 117 | options.add_argument("--log-level=3") 118 | options.add_argument("--disable-logging") 119 | options.add_experimental_option('useAutomationExtension', False) 120 | options.add_experimental_option('excludeSwitches', ['enable-automation']) 121 | data_dir = os.path.join(Farmer.chrome_data_dir, self.wax_account) 122 | options.add_argument("--user-data-dir={0}".format(data_dir)) 123 | if self.proxy: 124 | options.add_argument("--proxy-server={0}".format(self.proxy)) 125 | self.driver = webdriver.Chrome(plat.driver_path, options=options) 126 | self.driver.implicitly_wait(60) 127 | self.driver.set_script_timeout(60) 128 | self.http = requests.Session() 129 | self.http.trust_env = False 130 | self.http.request = functools.partial(self.http.request, timeout=30) 131 | if self.proxy: 132 | self.http.proxies = { 133 | "http": "http://{0}".format(self.proxy), 134 | "https": "http://{0}".format(self.proxy), 135 | } 136 | http_retry_wrapper = tenacity.retry(wait=wait_fixed(cfg.req_interval), stop=stop_after_attempt(5), 137 | retry=retry_if_exception_type(RequestException), 138 | before_sleep=self.log_retry, reraise=True) 139 | self.http.get = http_retry_wrapper(self.http.get) 140 | self.http.post = http_retry_wrapper(self.http.post) 141 | 142 | def inject_waxjs(self): 143 | # 如果已经注入过就不再注入了 144 | if self.driver.execute_script("return window.mywax != undefined;"): 145 | return True 146 | 147 | if not Farmer.waxjs: 148 | with open("waxjs.js", "r") as file: 149 | Farmer.waxjs = file.read() 150 | file.close() 151 | Farmer.waxjs = base64.b64encode(Farmer.waxjs.encode()).decode() 152 | if not Farmer.myjs: 153 | with open("inject.js", "r") as file: 154 | inject_rpc = "window.mywax = new waxjs.WaxJS({rpcEndpoint: '" + user_param.rpc_domain + "'});" 155 | Farmer.myjs = inject_rpc + file.read() 156 | file.close() 157 | 158 | code = "var s = document.createElement('script');" 159 | code += "s.type = 'text/javascript';" 160 | code += "s.text = atob('{0}');".format(Farmer.waxjs) 161 | code += "document.head.appendChild(s);" 162 | self.driver.execute_script(code) 163 | self.driver.execute_script(Farmer.myjs) 164 | return True 165 | 166 | def start(self): 167 | self.log.info("启动浏览器") 168 | self.log.info("wax节点: {0}".format(user_param.rpc_domain)) 169 | self.log.info("原子市场节点: {0}".format(user_param.assets_domain)) 170 | if self.cookies: 171 | self.log.info("使用预设的cookie自动登录") 172 | cookies = self.cookies["cookies"] 173 | key_cookie = {} 174 | for item in cookies: 175 | if item.get("domain") == "all-access.wax.io": 176 | key_cookie = item 177 | break 178 | if not key_cookie: 179 | raise CookieExpireException("not find cookie domain as all-access.wax.io") 180 | ret = self.driver.execute_cdp_cmd("Network.setCookie", key_cookie) 181 | self.log.info("Network.setCookie: {0}".format(ret)) 182 | if not ret["success"]: 183 | raise CookieExpireException("Network.setCookie error") 184 | self.driver.get("https://play.farmersworld.io/") 185 | # 等待页面加载完毕 186 | elem = self.driver.find_element(By.ID, "RPC-Endpoint") 187 | elem.find_element(By.XPATH, "option[contains(@name, 'https')]") 188 | wait_seconds = 60 189 | if self.may_cache_login(): 190 | self.log.info("使用Cache自动登录") 191 | else: 192 | wait_seconds = 600 193 | self.log.info("请在弹出的窗口中手动登录账号") 194 | # 点击登录按钮,点击WAX云钱包方式登录 195 | elem = self.driver.find_element(By.CLASS_NAME, "login-button") 196 | elem.click() 197 | elem = self.driver.find_element(By.CLASS_NAME, "login-button--text") 198 | elem.click() 199 | # 等待登录成功 200 | self.log.info("等待登录") 201 | WebDriverWait(self.driver, wait_seconds, 1).until( 202 | EC.presence_of_element_located((By.XPATH, "//img[@class='navbar-group--icon' and @alt='Map']"))) 203 | # self.driver.find_element(By.XPATH, "//img[@class='navbar-group--icon' and @alt='Map']") 204 | self.log.info("登录成功,稍等...") 205 | time.sleep(cfg.req_interval) 206 | self.inject_waxjs() 207 | ret = self.driver.execute_script("return window.wax_login();") 208 | self.log.info("window.wax_login(): {0}".format(ret)) 209 | if not ret[0]: 210 | raise CookieExpireException("cookie失效") 211 | 212 | # 从服务器获取游戏参数 213 | self.log.info("正在加载游戏配置") 214 | self.init_farming_config() 215 | time.sleep(cfg.req_interval) 216 | 217 | def may_cache_login(self): 218 | cookies = self.driver.execute_cdp_cmd("Network.getCookies", {"urls": ["https://all-access.wax.io"]}) 219 | for item in cookies["cookies"]: 220 | if item.get("name") == "token_id": 221 | return True 222 | return False 223 | 224 | def log_retry(self, state: RetryCallState): 225 | exp = state.outcome.exception() 226 | if isinstance(exp, RequestException): 227 | self.log.info("网络错误: {0}".format(exp)) 228 | self.log.info("正在重试: [{0}]".format(state.attempt_number)) 229 | 230 | def http_post(self, post_data): 231 | # rpc_domain = random.choice(user_param.rpc_domain_list) 232 | url_table_row = user_param.rpc_domain + '/v1/chain/get_table_rows' 233 | return self.http.post(url_table_row, json=post_data) 234 | 235 | def table_row_template(self) -> dict: 236 | post_data = { 237 | "json": True, 238 | "code": "farmersworld", 239 | "scope": "farmersworld", 240 | "table": None, # 覆写 241 | "lower_bound": self.wax_account, 242 | "upper_bound": self.wax_account, 243 | "index_position": None, # 覆写 244 | "key_type": "i64", 245 | "limit": 100, 246 | "reverse": False, 247 | "show_payer": False 248 | } 249 | return post_data 250 | 251 | # 从服务器获取各种工具和作物的参数 252 | def init_farming_config(self): 253 | # 工具 254 | post_data = { 255 | "json": True, 256 | "code": "farmersworld", 257 | "scope": "farmersworld", 258 | "table": "toolconfs", 259 | "lower_bound": "", 260 | "upper_bound": "", 261 | "index_position": 1, 262 | "key_type": "", 263 | "limit": 100, 264 | "reverse": False, 265 | "show_payer": False 266 | } 267 | resp = self.http_post(post_data) 268 | self.log.debug("get tools config:{0}".format(resp.text)) 269 | resp = resp.json() 270 | res.init_tool_config(resp["rows"]) 271 | time.sleep(cfg.req_interval) 272 | 273 | # 农作物 274 | post_data["table"] = "cropconf" 275 | resp = self.http_post(post_data) 276 | self.log.debug("get crop config:{0}".format(resp.text)) 277 | resp = resp.json() 278 | res.init_crop_config(resp["rows"]) 279 | # 动物 280 | post_data["table"] = "anmconf" 281 | resp = self.http_post(post_data) 282 | self.log.debug("get animal conf:{0}".format(resp.text)) 283 | resp = resp.json() 284 | res.init_animal_config(resp["rows"]) 285 | 286 | # 会员卡 287 | post_data["table"] = "mbsconf" 288 | resp = self.http_post(post_data) 289 | self.log.debug("get mbs config:{0}".format(resp.text)) 290 | resp = resp.json() 291 | res.init_mbs_config(resp["rows"]) 292 | 293 | # 从服务器获取配置 294 | def get_farming_config(self): 295 | post_data = { 296 | "json": True, 297 | "code": "farmersworld", 298 | "scope": "farmersworld", 299 | "table": "config", 300 | "lower_bound": "", 301 | "upper_bound": "", 302 | "index_position": 1, 303 | "key_type": "", 304 | "limit": 1, 305 | "reverse": False, 306 | "show_payer": False 307 | } 308 | resp = self.http_post(post_data) 309 | self.log.debug("get farming config:{0}".format(resp.text)) 310 | resp = resp.json() 311 | 312 | return resp["rows"][0] 313 | 314 | # 获取游戏中的三种资源数量和能量值 315 | def get_resource(self) -> Resoure: 316 | post_data = self.table_row_template() 317 | post_data["table"] = "accounts" 318 | post_data["index_position"] = 1 319 | 320 | resp = self.http_post(post_data) 321 | self.log.debug("get_table_rows:{0}".format(resp.text)) 322 | resp = resp.json() 323 | if len(resp["rows"]) == 0: 324 | self.log.info("===============================") 325 | self.log.info("获取不到账号数据,请检查账号名是否有误") 326 | self.log.info("===============================") 327 | resource = Resoure() 328 | resource.energy = Decimal(resp["rows"][0]["energy"]) 329 | resource.max_energy = Decimal(resp["rows"][0]["max_energy"]) 330 | resource.gold = Decimal(0) 331 | resource.wood = Decimal(0) 332 | resource.food = Decimal(0) 333 | balances: List[str] = resp["rows"][0]["balances"] 334 | for item in balances: 335 | sp = item.split(" ") 336 | if sp[1].upper() == "GOLD": 337 | resource.gold = Decimal(sp[0]) 338 | elif sp[1].upper() == "WOOD": 339 | resource.wood = Decimal(sp[0]) 340 | elif sp[1].upper() == "FOOD": 341 | resource.food = Decimal(sp[0]) 342 | self.log.debug("resource: {0}".format(resource)) 343 | return resource 344 | 345 | # 获取建造信息 346 | def get_buildings(self) -> List[Building]: 347 | post_data = self.table_row_template() 348 | post_data["table"] = "buildings" 349 | post_data["index_position"] = 2 350 | 351 | resp = self.http_post(post_data) 352 | self.log.debug("get_buildings_info:{0}".format(resp.text)) 353 | resp = resp.json() 354 | buildings = [] 355 | for item in resp["rows"]: 356 | build = Building() 357 | build.asset_id = item["asset_id"] 358 | build.name = item["name"] 359 | build.is_ready = item["is_ready"] 360 | build.next_availability = datetime.fromtimestamp(item["next_availability"]) 361 | build.template_id = item["template_id"] 362 | build.times_claimed = item.get("times_claimed", None) 363 | build.slots_used = item.get("slots_used", None) 364 | if build.is_ready == 1: 365 | continue 366 | buildings.append(build) 367 | return buildings 368 | 369 | # 获取农作物信息 370 | def get_crops(self) -> List[Crop]: 371 | post_data = self.table_row_template() 372 | post_data["table"] = "crops" 373 | post_data["index_position"] = 2 374 | 375 | resp = self.http_post(post_data) 376 | self.log.debug("get_crops_info:{0}".format(resp.text)) 377 | resp = resp.json() 378 | crops = [] 379 | for item in resp["rows"]: 380 | crop = res.create_crop(item) 381 | if crop: 382 | crops.append(crop) 383 | else: 384 | self.log.warning("尚未支持的农作物类型:{0}".format(item)) 385 | return crops 386 | 387 | # claim 建筑 388 | def claim_building(self, item: Building): 389 | self.consume_energy(Decimal(item.energy_consumed)) 390 | 391 | transaction = { 392 | "actions": [{ 393 | "account": "farmersworld", 394 | "name": "bldclaim", 395 | "authorization": [{ 396 | "actor": self.wax_account, 397 | "permission": "active", 398 | }], 399 | "data": { 400 | "asset_id": item.asset_id, 401 | "owner": self.wax_account, 402 | }, 403 | }], 404 | } 405 | return self.wax_transact(transaction) 406 | 407 | # 耕种农作物 408 | def claim_crop(self, crop: Crop): 409 | energy_consumed = crop.energy_consumed 410 | fake_consumed = Decimal(0) 411 | if crop.times_claimed == crop.required_claims - 1: 412 | # 收获前的最后一次耕作,多需要200点能量,游戏合约BUG(玉米需要245) 413 | fake_consumed = Decimal(250) 414 | self.consume_energy(Decimal(energy_consumed), fake_consumed) 415 | transaction = { 416 | "actions": [{ 417 | "account": "farmersworld", 418 | "name": "cropclaim", 419 | "authorization": [{ 420 | "actor": self.wax_account, 421 | "permission": "active", 422 | }], 423 | "data": { 424 | "crop_id": crop.asset_id, 425 | "owner": self.wax_account, 426 | }, 427 | }], 428 | } 429 | return self.wax_transact(transaction) 430 | 431 | def claim_buildings(self, blds: List[Building]): 432 | for item in blds: 433 | self.log.info("正在建造: {0}".format(item.show())) 434 | if self.claim_building(item): 435 | self.log.info("建造成功: {0}".format(item.show(more=False))) 436 | else: 437 | self.log.info("建造失败: {0}".format(item.show(more=False))) 438 | self.count_error_claim += 1 439 | time.sleep(cfg.req_interval) 440 | 441 | def claim_crops(self, crops: List[Crop]): 442 | for item in crops: 443 | self.log.info("正在耕作: {0}".format(item.show())) 444 | if self.claim_crop(item): 445 | self.log.info("耕作成功: {0}".format(item.show(more=False))) 446 | else: 447 | self.log.info("耕作失败: {0}".format(item.show(more=False))) 448 | self.count_error_claim += 1 449 | time.sleep(cfg.req_interval) 450 | 451 | # 获取箱子里的NTF 452 | def get_chest(self) -> dict: 453 | payload = { 454 | "limit": 1000, 455 | "collection_name": "farmersworld", 456 | "owner": self.wax_account, 457 | "template_blacklist": "260676", 458 | } 459 | resp = self.http.get(self.url_assets, params=payload) 460 | self.log.debug("get_chest:{0}".format(resp.text)) 461 | resp = resp.json() 462 | assert resp["success"] 463 | return resp 464 | 465 | # schema: [foods] 466 | def get_chest_by_schema_name(self, schema_name: str): 467 | payload = { 468 | "limit": 1000, 469 | "collection_name": "farmersworld", 470 | "owner": self.wax_account, 471 | "schema_name": schema_name, 472 | } 473 | resp = self.http.get(self.url_assets, params=payload) 474 | self.log.debug("get_chest_by_schema_name:{0}".format(resp.text)) 475 | resp = resp.json() 476 | assert resp["success"] 477 | return resp 478 | 479 | # template_id: [大麦 318606] [玉米 318607] 480 | def get_chest_by_template_id(self, template_id: int): 481 | payload = { 482 | "limit": 1000, 483 | "collection_name": "farmersworld", 484 | "owner": self.wax_account, 485 | "template_id": template_id, 486 | } 487 | resp = self.http.get(self.url_assets, params=payload) 488 | self.log.debug("get_chest_by_template_id:{0}".format(resp.text)) 489 | resp = resp.json() 490 | assert resp["success"] 491 | return resp 492 | 493 | # 获取大麦 494 | def get_barley(self) -> List[Asset]: 495 | barley_list = self.get_asset(NFT.Barley, 'Barley') 496 | return barley_list 497 | 498 | # 获取牛奶 499 | def get_milk(self) -> List[Asset]: 500 | milk_list = self.get_asset(NFT.Milk, 'Milk') 501 | return milk_list 502 | 503 | # 获取鸡蛋 504 | def get_egg(self) -> List[Asset]: 505 | egg_list = self.get_asset(NFT.ChickenEgg, 'ChickenEgg') 506 | return egg_list 507 | 508 | # 获取玉米 509 | def get_corn(self) -> List[Asset]: 510 | corn_list = self.get_asset(NFT.Corn, 'Corn') 511 | return corn_list 512 | 513 | # 获取NFT资产,可以是小麦,小麦种子,牛奶等 514 | def get_asset(self, template_id, name) -> List[Asset]: 515 | asset_list = [] 516 | chest = self.get_chest_by_template_id(template_id) 517 | if len(chest["data"]) <= 0: 518 | return asset_list 519 | for item in chest["data"]: 520 | asset = Asset() 521 | asset.asset_id = item["asset_id"] 522 | asset.name = item["name"] 523 | asset.is_transferable = item["is_transferable"] 524 | asset.is_burnable = item["is_transferable"] 525 | asset.schema_name = item["schema"]["schema_name"] 526 | asset.template_id = item["template"]["template_id"] 527 | asset_list.append(asset) 528 | self.log.debug("[{0}]_get_asset_list: [{1}]".format(name, format(asset_list))) 529 | return asset_list 530 | 531 | # 获取动物的信息 532 | def get_breedings(self) -> List[Animal]: 533 | post_data = self.table_row_template() 534 | post_data["table"] = "breedings" 535 | post_data["index_position"] = 2 536 | 537 | resp = self.http_post(post_data) 538 | self.log.debug("get_breedings:{0}".format(resp.text)) 539 | resp = resp.json() 540 | if len(resp["rows"]) == 0: 541 | self.log.warning("没有正在繁殖的动物,请先手动开启繁殖") 542 | animals = [] 543 | for item in resp["rows"]: 544 | anim = res.create_animal(item, True) 545 | if anim: 546 | animals.append(anim) 547 | else: 548 | self.log.info("尚未支持繁殖的动物") 549 | return animals 550 | 551 | def get_animals(self) -> List[Animal]: 552 | post_data = self.table_row_template() 553 | post_data["table"] = "animals" 554 | post_data["index_position"] = 2 555 | 556 | resp = self.http_post(post_data) 557 | self.log.debug("get_animal_info:{0}".format(resp.text)) 558 | resp = resp.json() 559 | if len(resp["rows"]) == 0: 560 | self.log.warning("账户中没有动物") 561 | animals = [] 562 | for item in resp["rows"]: 563 | anim = res.create_animal(item) 564 | if anim: 565 | if anim.required_building == 298590 and user_param.cow: 566 | # 牛棚 567 | animals.append(anim) 568 | elif anim.required_building == 298591 and user_param.chicken: 569 | # 鸡舍 570 | animals.append(anim) 571 | else: 572 | self.log.info("尚未支持的动物:{0}".format(item["name"])) 573 | 574 | return animals 575 | 576 | # 喂动物 577 | def feed_animal(self, asset_id_food: str, animal: Animal, breeding=False) -> bool: 578 | 579 | fake_consumed = Decimal(0) 580 | if animal.times_claimed == animal.required_claims - 1: 581 | # 收获前的最后一次喂养,多需要200点能量,游戏合约BUG 582 | fake_consumed = Decimal(200) 583 | self.consume_energy(Decimal(animal.energy_consumed), fake_consumed) 584 | if not breeding: 585 | self.log.info("feed [{0}] to [{1}]".format(asset_id_food, animal.asset_id)) 586 | memo = "feed_animal:{0}".format(animal.asset_id) 587 | else: 588 | self.log.info("feed [{0}] to [{1}]".format(asset_id_food, animal.bearer_id)) 589 | memo = "breed_animal:{0},{1}".format(animal.bearer_id, animal.partner_id) 590 | 591 | transaction = { 592 | "actions": [{ 593 | "account": "atomicassets", 594 | "name": "transfer", 595 | "authorization": [{ 596 | "actor": self.wax_account, 597 | "permission": "active", 598 | }], 599 | "data": { 600 | "asset_ids": [asset_id_food], 601 | "from": self.wax_account, 602 | "memo": memo, 603 | "to": "farmersworld" 604 | }, 605 | }], 606 | } 607 | return self.wax_transact(transaction) 608 | 609 | # 获取动物需要的食物 610 | def get_animal_food(self, animal: Animal): 611 | food_class = res.farming_table.get(animal.consumed_card) 612 | list_food = self.get_asset(animal.consumed_card, food_class.name) 613 | self.log.info("剩余[{0}]数量: [{1}]".format(food_class.name, len(list_food))) 614 | if len(list_food) <= 0: 615 | rs = self.buy_corps(animal.consumed_card, user_param.buy_food_num) 616 | if not rs: 617 | self.log.warning("{0}数量不足,请及时补充".format(food_class.name)) 618 | return False 619 | else: 620 | list_food = self.get_asset(animal.consumed_card, food_class.name) 621 | asset = list_food.pop() 622 | 623 | return asset.asset_id 624 | 625 | # 饲养动物 626 | def claim_animal(self, animals: List[Animal]): 627 | for item in animals: 628 | self.log.info("正在喂[{0}]: [{1}]".format(item.name, item.show())) 629 | if 'Egg' in item.name: 630 | success = self.care_animal(item) 631 | else: 632 | feed_asset_id = self.get_animal_food(item) 633 | if not feed_asset_id: 634 | return False 635 | success = self.feed_animal(feed_asset_id, item) 636 | 637 | if success: 638 | self.log.info("喂养成功: {0}".format(item.show(more=False))) 639 | else: 640 | self.log.info("喂养失败: {0}".format(item.show(more=False))) 641 | self.count_error_claim += 1 642 | time.sleep(cfg.req_interval) 643 | return True 644 | 645 | # 饲养繁殖的动物 646 | def breeding_claim(self, animals: List[Animal]): 647 | 648 | for item in animals: 649 | self.log.info("【繁殖】正在喂[{0}]: [{1}]".format(item.name, item.show(False, True))) 650 | feed_asset_id = self.get_animal_food(item) 651 | if not feed_asset_id: 652 | return False 653 | success = self.feed_animal(feed_asset_id, item, True) 654 | 655 | if success: 656 | self.log.info("【繁殖】喂养成功: {0}".format(item.show(more=False, breeding=True))) 657 | else: 658 | self.log.info("【繁殖】喂养失败: {0}".format(item.show(more=False, breeding=True))) 659 | self.count_error_claim += 1 660 | time.sleep(cfg.req_interval) 661 | return True 662 | 663 | def care_animal(self, animal: Animal): 664 | self.log.info("care_animal {0}".format(animal.asset_id)) 665 | fake_consumed = Decimal(0) 666 | if animal.times_claimed == animal.required_claims - 1: 667 | # 收获前的最后一次喂养,多需要200点能量,游戏合约BUG 668 | fake_consumed = Decimal(200) 669 | self.consume_energy(Decimal(animal.energy_consumed), fake_consumed) 670 | 671 | transaction = { 672 | "actions": [{ 673 | "account": "farmersworld", 674 | "name": "anmclaim", 675 | "authorization": [{ 676 | "actor": self.wax_account, 677 | "permission": "active", 678 | }], 679 | "data": { 680 | "animal_id": animal.asset_id, 681 | "owner": self.wax_account, 682 | }, 683 | }], 684 | } 685 | return self.wax_transact(transaction) 686 | 687 | # 获取wax账户信息 688 | def wax_get_account(self): 689 | url = self.url_rpc + "get_account" 690 | post_data = {"account_name": self.wax_account} 691 | resp = self.http.post(url, json=post_data) 692 | self.log.debug("get_account:{0}".format(resp.text)) 693 | resp = resp.json() 694 | return resp 695 | 696 | # 获取三种资源的代币余额 FWF FWG FWW 697 | def get_fw_balance(self) -> Token: 698 | url = self.url_rpc + "get_currency_balance" 699 | post_data = { 700 | "code": "farmerstoken", 701 | "account": self.wax_account, 702 | "symbol": None 703 | } 704 | resp = self.http.post(url, json=post_data) 705 | 706 | self.log.debug("get_fw_balance:{0}".format(resp.text)) 707 | resp = resp.json() 708 | balance = Token() 709 | balance.fwf = 0 710 | balance.fwg = 0 711 | balance.fww = 0 712 | for item in resp: 713 | sp = item.split(" ") 714 | if sp[1].upper() == "FWF": 715 | balance.fwf = Decimal(sp[0]) 716 | elif sp[1].upper() == "FWG": 717 | balance.fwg = Decimal(sp[0]) 718 | elif sp[1].upper() == "FWW": 719 | balance.fww = Decimal(sp[0]) 720 | self.log.debug("fw_balance: {0}".format(balance)) 721 | return balance 722 | 723 | # 签署交易(只许成功,否则抛异常) 724 | def wax_transact(self, transaction: dict): 725 | self.inject_waxjs() 726 | self.log.info("begin transact: {0}".format(transaction)) 727 | try: 728 | success, result = self.driver.execute_script("return window.wax_transact(arguments[0]);", transaction) 729 | if success: 730 | self.log.info("transact ok, transaction_id: [{0}]".format(result["transaction_id"])) 731 | self.log.debug("transact result: {0}".format(result)) 732 | time.sleep(cfg.transact_interval) 733 | return result 734 | else: 735 | if "is greater than the maximum billable" in result: 736 | self.log.error("CPU资源不足,可能需要质押更多WAX,一般为误报,稍后重试 maximum") 737 | elif "estimated CPU time (0 us) is not less than the maximum billable CPU time for the transaction (0 us)" in result: 738 | self.log.error("CPU资源不足,可能需要质押更多WAX,一般为误报,稍后重试 estimated") 739 | else: 740 | self.log.error("transact error: {0}".format(result)) 741 | raise TransactException(result) 742 | 743 | except WebDriverException as e: 744 | self.log.error("transact error: {0}".format(e)) 745 | self.log.exception(str(e)) 746 | raise TransactException(result) 747 | 748 | # 过滤可操作的作物 749 | def filter_operable(self, items: List[Farming]) -> Farming: 750 | now = datetime.now() 751 | op = [] 752 | for item in items: 753 | if isinstance(item, Building): 754 | if item.is_ready == 1: 755 | continue 756 | # daily_claim_limit 鸡24小时内最多喂4次 ,奶牛24小时内最多喂6次,小牛犊24小时内最多喂2次 757 | if isinstance(item, Animal): 758 | if len(item.day_claims_at) >= item.daily_claim_limit: 759 | next_op_time = item.day_claims_at[0] + timedelta(hours=24) 760 | item.next_availability = max(item.next_availability, next_op_time) 761 | self.log.info("[{0}]24小时内最多喂[{1}]次 ".format(item.name, item.daily_claim_limit)) 762 | if now < item.next_availability: 763 | self.not_operational.append(item) 764 | continue 765 | op.append(item) 766 | 767 | return op 768 | 769 | def scan_buildings(self): 770 | self.log.info("检查建筑物") 771 | buildings = self.get_buildings() 772 | if not buildings: 773 | self.log.info("没有未完成的建筑物") 774 | return True 775 | self.log.info("未完成的建筑物:") 776 | for item in buildings: 777 | self.log.info(item.show()) 778 | buildings = self.filter_operable(buildings) 779 | if not buildings: 780 | self.log.info("没有可操作的建筑物") 781 | return True 782 | self.log.info("可操作的建筑物:") 783 | for item in buildings: 784 | self.log.info(item.show()) 785 | self.claim_buildings(buildings) 786 | return True 787 | 788 | def scan_plants(self): 789 | self.log.info("自动种地") 790 | post_data = self.table_row_template() 791 | post_data["table"] = "buildings" 792 | post_data["index_position"] = 2 793 | 794 | resp = self.http_post(post_data) 795 | self.log.debug("get_buildings_info:{0}".format(resp.text)) 796 | resp = resp.json() 797 | for item in resp["rows"]: 798 | if item["template_id"] == 298592 and item["is_ready"] == 1: 799 | slots_num = 8 - item["slots_used"] 800 | if slots_num > 0: 801 | self.plant_corps(slots_num) 802 | else: 803 | self.log.info("没有未使用的地块") 804 | return True 805 | 806 | # 购买作物 807 | def buy_corps(self, template_id: int, buy_num: int): 808 | if buy_num <= 0: 809 | self.log.info("购买数量为0") 810 | return False 811 | item_class = res.farming_table.get(template_id) 812 | total_golds = item_class.golds_cost * buy_num 813 | if total_golds > self.resoure.gold: 814 | new_buy_num = int(self.resoure.gold / item_class.golds_cost) 815 | if new_buy_num <= 0: 816 | self.log.info("金币不足,无法购买,请先补充金币") 817 | return False 818 | else: 819 | self.log.info("金币不足,需要购买[{0}]个,实际购买[{1}]个".format(buy_num, new_buy_num)) 820 | buy_num = new_buy_num 821 | 822 | if user_param.buy_barley_seed and template_id == 298595: 823 | self.log.info("开始购买大麦种子,数量:{0}".format(buy_num)) 824 | self.market_buy(template_id, buy_num) 825 | elif user_param.buy_corn_seed and template_id == 298596: 826 | self.log.info("开始购买玉米种子,数量:{0}".format(buy_num)) 827 | self.market_buy(template_id, buy_num) 828 | elif user_param.buy_food and template_id == 318606: 829 | self.log.info("开始购买大麦,数量:{0}".format(buy_num)) 830 | self.market_buy(template_id, buy_num) 831 | elif user_param.buy_food and template_id == 318607: 832 | self.log.info("开始购买玉米,数量:{0}".format(buy_num)) 833 | self.market_buy(template_id, buy_num) 834 | else: 835 | self.log.info("配置不执行购买,请检查") 836 | 837 | time.sleep(2) 838 | return True 839 | 840 | # 市场购买 841 | def market_buy(self, template_id: int, buy_num: int): 842 | 843 | transaction = { 844 | "actions": [{ 845 | "account": "farmersworld", 846 | "name": "mktbuy", 847 | "authorization": [{ 848 | "actor": self.wax_account, 849 | "permission": "active", 850 | }], 851 | "data": { 852 | "owner": self.wax_account, 853 | "quantity": buy_num, 854 | "template_id": template_id, 855 | }, 856 | }], 857 | } 858 | self.wax_transact(transaction) 859 | self.log.info("购买完成") 860 | 861 | return True 862 | 863 | # 种植 864 | def plant_corps(self, slots_num): 865 | self.log.info("获取大麦或玉米种子") 866 | if user_param.barleyseed_num > 0: 867 | barleyseed_list = self.get_asset(298595, 'Barley Seed') 868 | plant_times = min(slots_num, user_param.barleyseed_num) 869 | if len(barleyseed_list) < plant_times and user_param.buy_barley_seed: 870 | self.log.warning("大麦种子数量不足,开始市场购买") 871 | buy_barleyseed_num = plant_times - len(barleyseed_list) 872 | rs = self.buy_corps(298595, buy_barleyseed_num) 873 | if not rs: 874 | return False 875 | else: 876 | barleyseed_list = self.get_asset(298595, 'Barley Seed') 877 | if len(barleyseed_list) > 0: 878 | for i in range(plant_times): 879 | asset = barleyseed_list.pop() 880 | self.wear_assets([asset.asset_id]) 881 | else: 882 | self.log.info("大麦种子数量不足,请及时补充") 883 | else: 884 | self.log.info("设置的大麦种子数量为0") 885 | 886 | if user_param.cornseed_num > 0: 887 | cornseed_list = self.get_asset(298596, 'Corn Seed') 888 | plant_times2 = min(slots_num, user_param.cornseed_num) 889 | if len(cornseed_list) < plant_times2 and user_param.buy_corn_seed: 890 | self.log.warning("玉米种子数量不足,开始市场购买") 891 | buy_cornseed_num = plant_times2 - len(cornseed_list) 892 | rs = self.buy_corps(298596, buy_cornseed_num) 893 | if not rs: 894 | return False 895 | else: 896 | cornseed_list = self.get_asset(298596, 'Corn Seed') 897 | if len(cornseed_list) > 0: 898 | for i in range(plant_times2): 899 | asset = cornseed_list.pop() 900 | self.wear_assets([asset.asset_id]) 901 | else: 902 | self.log.info("玉米种子数量不足,请及时补充") 903 | else: 904 | self.log.info("设置的玉米种子数量为0") 905 | 906 | return True 907 | 908 | # 穿戴工具,种地-(种地:玉米、小麦) 909 | def wear_assets(self, asset_ids): 910 | self.log.info("正在种地【玉米种子|小麦种子】") 911 | transaction = { 912 | "actions": [{ 913 | "account": "atomicassets", 914 | "name": "transfer", 915 | "authorization": [{ 916 | "actor": self.wax_account, 917 | "permission": "active", 918 | }], 919 | "data": { 920 | "from": self.wax_account, 921 | "to": "farmersworld", 922 | "asset_ids": asset_ids, 923 | "memo": "stake", 924 | }, 925 | }], 926 | } 927 | self.wax_transact(transaction) 928 | self.log.info("种地完成") 929 | time.sleep(cfg.req_interval) 930 | 931 | def scan_crops(self): 932 | self.log.info("检查农田") 933 | crops = self.get_crops() 934 | if not crops: 935 | self.log.info("没有农作物") 936 | return True 937 | self.log.info("种植的农作物:") 938 | for item in crops: 939 | self.log.info(item.show()) 940 | crops = self.filter_operable(crops) 941 | if not crops: 942 | self.log.info("没有可操作的农作物") 943 | return True 944 | self.log.info("可操作的农作物:") 945 | for item in crops: 946 | self.log.info(item.show()) 947 | self.claim_crops(crops) 948 | return True 949 | 950 | # 售卖玉米和大麦 951 | def scan_nft_assets(self): 952 | asset_ids = [] 953 | sell_barley_num = 0 954 | sell_corn_num = 0 955 | sell_milk_num = 0 956 | sell_egg_num = 0 957 | if user_param.sell_corn: 958 | self.log.info("检查玉米NFT") 959 | list_corn = self.get_corn() 960 | self.log.info("剩余玉米数量: {0}".format(len(list_corn))) 961 | if len(list_corn) > 0: 962 | for item in list_corn: 963 | if len(list_corn) - sell_corn_num <= user_param.remaining_corn_num: 964 | break 965 | asset_ids.append(item.asset_id) 966 | sell_corn_num = sell_corn_num + 1 967 | 968 | if user_param.sell_barley: 969 | self.log.info("检查大麦") 970 | list_barley = self.get_barley() 971 | self.log.info("剩余大麦数量: {0}".format(len(list_barley))) 972 | if len(list_barley) > 0: 973 | for item in list_barley: 974 | if len(list_barley) - sell_barley_num <= user_param.remaining_barley_num: 975 | break 976 | asset_ids.append(item.asset_id) 977 | sell_barley_num = sell_barley_num + 1 978 | if user_param.sell_milk: 979 | self.log.info("检查牛奶") 980 | list_milk = self.get_milk() 981 | self.log.info("剩余牛奶数量: {0}".format(len(list_milk))) 982 | if len(list_milk) > 0: 983 | for item in list_milk: 984 | if len(list_milk) - sell_milk_num <= user_param.remaining_milk_num: 985 | break 986 | asset_ids.append(item.asset_id) 987 | sell_milk_num = sell_milk_num + 1 988 | 989 | if user_param.sell_egg: 990 | self.log.info("检查鸡蛋") 991 | list_egg = self.get_egg() 992 | self.log.info("剩余鸡蛋数量: {0}".format(len(list_egg))) 993 | if len(list_egg) > 0: 994 | for item in list_egg: 995 | if len(list_egg) - sell_egg_num <= user_param.remaining_egg_num: 996 | break 997 | asset_ids.append(item.asset_id) 998 | sell_egg_num = sell_egg_num + 1 999 | 1000 | if len(asset_ids) <= 0: 1001 | self.log.warning("没有可售卖的NFT资产【玉米|小麦|牛奶|鸡蛋】") 1002 | return True 1003 | 1004 | self.burn_assets(asset_ids) 1005 | self.log.warning( 1006 | "共卖出数量:[{0}],玉米[{1}],大麦[{2}],牛奶[{3}],鸡蛋[{4}]".format(len(asset_ids), sell_corn_num, sell_barley_num, 1007 | sell_milk_num, sell_egg_num)) 1008 | return True 1009 | 1010 | # 卖资产-玉米、小麦和牛奶 1011 | def burn_assets(self, asset_ids): 1012 | self.log.info("正在卖资产【玉米|小麦|牛奶|鸡蛋】") 1013 | transaction = { 1014 | "actions": [{ 1015 | "account": "atomicassets", 1016 | "name": "transfer", 1017 | "authorization": [{ 1018 | "actor": self.wax_account, 1019 | "permission": "active", 1020 | }], 1021 | "data": { 1022 | "from": self.wax_account, 1023 | "to": "farmersworld", 1024 | "asset_ids": asset_ids, 1025 | "memo": "burn", 1026 | }, 1027 | }], 1028 | } 1029 | self.wax_transact(transaction) 1030 | self.log.info("售卖已完成") 1031 | time.sleep(cfg.req_interval) 1032 | 1033 | def scan_breedings(self): 1034 | self.log.info("检查繁殖的动物") 1035 | breedings = self.get_breedings() 1036 | self.log.info("饲养繁殖的动物:") 1037 | for item in breedings: 1038 | self.log.info(item.show()) 1039 | breedings = self.filter_operable(breedings) 1040 | if not breedings: 1041 | self.log.info("没有可操作繁殖的动物") 1042 | return True 1043 | self.log.info("可操作繁殖的动物:") 1044 | for item in breedings: 1045 | self.log.info(item.show()) 1046 | self.breeding_claim(breedings) 1047 | return True 1048 | 1049 | def scan_animals(self): 1050 | self.log.info("检查动物") 1051 | animals = self.get_animals() 1052 | self.log.info("饲养的动物:") 1053 | for item in animals: 1054 | self.log.info(item.show()) 1055 | animals = self.filter_operable(animals) 1056 | if not animals: 1057 | self.log.info("没有可操作的动物") 1058 | return True 1059 | self.log.info("可操作的动物:") 1060 | for item in animals: 1061 | self.log.info(item.show()) 1062 | self.claim_animal(animals) 1063 | return True 1064 | 1065 | def get_tools(self): 1066 | post_data = self.table_row_template() 1067 | post_data["table"] = "tools" 1068 | post_data["index_position"] = 2 1069 | 1070 | resp = self.http_post(post_data) 1071 | self.log.debug("get_tools:{0}".format(resp.text)) 1072 | resp = resp.json() 1073 | tools = [] 1074 | for item in resp["rows"]: 1075 | tool = res.create_tool(item) 1076 | if tool: 1077 | tools.append(tool) 1078 | else: 1079 | self.log.warning("尚未支持的工具类型:{0}".format(item)) 1080 | return tools 1081 | 1082 | # 使用工具挖矿操作1 1083 | def claim_mining(self, tools: List[Tool]): 1084 | enough_tools = [] 1085 | not_enough_tools = [] 1086 | for item in tools: 1087 | check_status = self.check_durability(item) 1088 | if check_status: 1089 | enough_tools.append(item) 1090 | else: 1091 | not_enough_tools.append(item) 1092 | # 耐久度够的先处理 1093 | self.do_mining(enough_tools) 1094 | # 耐久度不够的后处理 1095 | self.do_mining(not_enough_tools) 1096 | 1097 | # 使用工具挖矿操作2 1098 | def do_mining(self, tools: List[Tool]): 1099 | for item in tools: 1100 | self.log.info("正在采矿: {0}".format(item.show())) 1101 | self.consume_energy(Decimal(item.energy_consumed)) 1102 | self.consume_durability(item) 1103 | transaction = { 1104 | "actions": [{ 1105 | "account": "farmersworld", 1106 | "name": "claim", 1107 | "authorization": [{ 1108 | "actor": self.wax_account, 1109 | "permission": "active", 1110 | }], 1111 | "data": { 1112 | "asset_id": item.asset_id, 1113 | "owner": self.wax_account, 1114 | }, 1115 | }], 1116 | } 1117 | self.wax_transact(transaction) 1118 | # ming_resource = result["processed"]["action_traces"][0]["inline_traces"][1]["act"]["data"]["rewards"] 1119 | # self.log.info("采矿成功: {0},{1}".format(item.show(more=False), ming_resource)) 1120 | self.log.info("采矿成功: {0}".format(item.show(more=False))) 1121 | time.sleep(cfg.req_interval) 1122 | 1123 | def scan_mining(self): 1124 | self.log.info("检查矿场") 1125 | tools = self.get_tools() 1126 | self.log.info("采矿的工具:") 1127 | if user_param.mbs and user_param.mbs_mint: 1128 | self.log.info("已开启会员卡存储挖矿") 1129 | 1130 | for item in tools: 1131 | if user_param.mbs and user_param.mbs_mint: 1132 | if item.mining_type == 'Wood': 1133 | item.next_availability = item.next_availability + item.charge_time * self.mbs_saved_claims.Wood 1134 | item.energy_consumed = item.energy_consumed * (self.mbs_saved_claims.Wood+1) 1135 | item.durability_consumed = item.durability_consumed * (self.mbs_saved_claims.Wood+1) 1136 | if item.mining_type == 'Food': 1137 | item.next_availability = item.next_availability + item.charge_time * self.mbs_saved_claims.Food 1138 | item.energy_consumed = item.energy_consumed * (self.mbs_saved_claims.Food + 1) 1139 | item.durability_consumed = item.durability_consumed * (self.mbs_saved_claims.Food + 1) 1140 | if item.mining_type == 'Gold': 1141 | item.next_availability = item.next_availability + item.charge_time * self.mbs_saved_claims.Gold 1142 | item.energy_consumed = item.energy_consumed * (self.mbs_saved_claims.Gold + 1) 1143 | item.durability_consumed = item.durability_consumed * (self.mbs_saved_claims.Gold + 1) 1144 | self.log.info(item.show()) 1145 | tools = self.filter_operable(tools) 1146 | if not tools: 1147 | self.log.info("没有可操作的采矿工具") 1148 | return True 1149 | self.log.info("可操作的采矿工具:") 1150 | for item in tools: 1151 | self.log.info(item.show()) 1152 | self.claim_mining(tools) 1153 | return True 1154 | 1155 | # 充值 1156 | def scan_deposit(self): 1157 | self.log.info("检查是否需要充值") 1158 | r = self.resoure 1159 | 1160 | deposit_wood = 0 1161 | deposit_food = 0 1162 | deposit_gold = 0 1163 | 1164 | if r.wood <= user_param.fww_min: 1165 | deposit_wood = user_param.deposit_fww 1166 | if 0 < self.token.fww < deposit_wood: 1167 | deposit_wood = self.token.fww 1168 | self.log.info(f"fww不足,剩余{deposit_wood}个fww代币将全部充值") 1169 | elif self.token.fww == 0 and deposit_wood > 0: 1170 | self.log.info(f"fww为0,请先购买{deposit_wood}个fww代币") 1171 | return False 1172 | if r.gold <= user_param.fwg_min: 1173 | deposit_gold = user_param.deposit_fwg 1174 | if 0 < self.token.fwg < deposit_gold: 1175 | deposit_gold = self.token.fwg 1176 | self.log.info(f"fwg不足,剩余{deposit_gold}个fwg代币将全部充值") 1177 | elif self.token.fwg == 0 and deposit_gold > 0: 1178 | self.log.info(f"fwg为0,请先购买{deposit_gold}个fwg代币") 1179 | return False 1180 | if r.food <= user_param.fwf_min: 1181 | deposit_food = user_param.deposit_fwf 1182 | if 0 < self.token.fwf < deposit_food: 1183 | deposit_food = self.token.fwf 1184 | self.log.info(f"fwf不足,剩余{deposit_food}个fwf代币将全部充值") 1185 | elif self.token.fwf == 0 and deposit_food > 0: 1186 | self.log.info(f"fwf为0,请先购买{deposit_food}个fwf代币") 1187 | return False 1188 | if deposit_wood + deposit_food + deposit_gold == 0: 1189 | self.log.info("充值数量为0,无需充值") 1190 | else: 1191 | self.do_deposit(deposit_food, deposit_gold, deposit_wood) 1192 | self.log.info(f"充值:金币【{deposit_gold}】 木头【{deposit_wood}】 食物【{deposit_food}】 ") 1193 | 1194 | return True 1195 | 1196 | # 充值 1197 | def do_deposit(self, food, gold, wood): 1198 | self.log.info("正在充值") 1199 | # format(1.23456, '.4f') 1200 | quantities = [] 1201 | if food > 0: 1202 | food = format(food, '.4f') 1203 | quantities.append(food + " FWF") 1204 | if gold > 0: 1205 | gold = format(gold, '.4f') 1206 | quantities.append(gold + " FWG") 1207 | if wood > 0: 1208 | wood = format(wood, '.4f') 1209 | quantities.append(wood + " FWW") 1210 | # quantities格式:1.0000 FWW 1211 | transaction = { 1212 | "actions": [{ 1213 | "account": "farmerstoken", 1214 | "name": "transfers", 1215 | "authorization": [{ 1216 | "actor": self.wax_account, 1217 | "permission": "active", 1218 | }], 1219 | "data": { 1220 | "from": self.wax_account, 1221 | "to": "farmersworld", 1222 | "quantities": quantities, 1223 | "memo": "deposit", 1224 | }, 1225 | }], 1226 | } 1227 | self.wax_transact(transaction) 1228 | self.log.info("充值完成") 1229 | 1230 | # 提现 1231 | def do_withdraw(self, food, gold, wood, fee): 1232 | self.log.info("正在提现") 1233 | # format(1.23456, '.4f') 1234 | quantities = [] 1235 | if food > 0: 1236 | food = format(food, '.4f') 1237 | quantities.append(food + " FOOD") 1238 | if gold > 0: 1239 | gold = format(gold, '.4f') 1240 | quantities.append(gold + " GOLD") 1241 | if wood > 0: 1242 | wood = format(wood, '.4f') 1243 | quantities.append(wood + " WOOD") 1244 | 1245 | # 格式:1.0000 WOOD 1246 | transaction = { 1247 | "actions": [{ 1248 | "account": "farmersworld", 1249 | "name": "withdraw", 1250 | "authorization": [{ 1251 | "actor": self.wax_account, 1252 | "permission": "active", 1253 | }], 1254 | "data": { 1255 | "owner": self.wax_account, 1256 | "quantities": quantities, 1257 | "fee": fee, 1258 | }, 1259 | }], 1260 | } 1261 | self.wax_transact(transaction) 1262 | self.log.info("提现完成") 1263 | 1264 | # 修理工具 1265 | def repair_tool(self, tool: Tool): 1266 | self.log.info(f"正在修理工具: {tool.show()}") 1267 | consume_gold = (tool.durability - tool.current_durability) // 5 1268 | if Decimal(consume_gold) > self.resoure.gold: 1269 | raise FarmerException("没有足够的金币修理工具,请补充金币,稍后程序自动重试") 1270 | 1271 | transaction = { 1272 | "actions": [{ 1273 | "account": "farmersworld", 1274 | "name": "repair", 1275 | "authorization": [{ 1276 | "actor": self.wax_account, 1277 | "permission": "active", 1278 | }], 1279 | "data": { 1280 | "asset_id": tool.asset_id, 1281 | "asset_owner": self.wax_account, 1282 | }, 1283 | }], 1284 | } 1285 | self.wax_transact(transaction) 1286 | self.log.info(f"修理完毕: {tool.show(more=False)}") 1287 | 1288 | # 恢复能量 1289 | def recover_energy(self, count: Decimal): 1290 | self.log.info("正在恢复能量: 【{0}】点 ".format(count)) 1291 | need_food = count // Decimal(5) 1292 | if need_food > self.resoure.food: 1293 | if self.resoure.food <= 0: 1294 | # 食物不足,开启充值 1295 | if user_param.auto_deposit: 1296 | self.log.info("食物不足,开启充值") 1297 | self.scan_deposit() 1298 | else: 1299 | self.log.info(f"食物不足,未开启充值,仅剩【{self.resoure.food}】,兑换能量【{count}】点需要【{need_food}】个食物,请手工处理") 1300 | raise FarmerException("没有足够的食物,请补充食物,稍后程序自动重试") 1301 | else: 1302 | count = self.resoure.food * Decimal(5) 1303 | self.log.info(f"食物不足,剩余【{self.resoure.food}】肉将全部补充能量,可补充【{count}】点") 1304 | 1305 | transaction = { 1306 | "actions": [{ 1307 | "account": "farmersworld", 1308 | "name": "recover", 1309 | "authorization": [{ 1310 | "actor": self.wax_account, 1311 | "permission": "active", 1312 | }], 1313 | "data": { 1314 | "energy_recovered": int(count), 1315 | "owner": self.wax_account, 1316 | }, 1317 | }], 1318 | } 1319 | return self.wax_transact(transaction) 1320 | 1321 | # 消耗能量 (操作前模拟计算) 1322 | def consume_energy(self, real_consume: Decimal, fake_consume: Decimal = Decimal(0)): 1323 | consume = real_consume + fake_consume 1324 | if self.resoure.energy - consume >= 0: 1325 | self.resoure.energy -= real_consume 1326 | return True 1327 | else: 1328 | self.log.info("能量不足") 1329 | recover = min(user_param.recover_energy, self.resoure.max_energy) - self.resoure.energy 1330 | recover = (recover // Decimal(5)) * Decimal(5) 1331 | self.recover_energy(recover) 1332 | self.resoure.energy += recover 1333 | self.resoure.energy -= real_consume 1334 | return True 1335 | 1336 | # 消耗耐久度 (操作前模拟计算) 1337 | def consume_durability(self, tool: Tool): 1338 | check_status = self.check_durability(tool) 1339 | if check_status: 1340 | return True 1341 | else: 1342 | self.log.info("工具耐久不足") 1343 | self.repair_tool(tool) 1344 | 1345 | # 判断耐久度 (操作前模拟计算) 1346 | def check_durability(self, tool: Tool): 1347 | if tool.current_durability / tool.durability < (user_param.min_durability / 100): 1348 | return False 1349 | elif tool.current_durability < tool.durability_consumed: 1350 | return False 1351 | else: 1352 | return True 1353 | 1354 | def scan_mbs(self): 1355 | self.log.info("检查会员卡") 1356 | mbs = self.get_mbs() 1357 | for item in mbs: 1358 | self.log.info(item.show(True)) 1359 | 1360 | mbs = self.filter_operable(mbs) 1361 | if not mbs: 1362 | self.log.info("没有可操作的会员卡") 1363 | return True 1364 | self.log.info("可操作的会员卡:") 1365 | for item in mbs: 1366 | self.log.info(item.show(True)) 1367 | self.claim_mbs(mbs) 1368 | return True 1369 | 1370 | def get_mbs(self) -> List[MBS]: 1371 | post_data = self.table_row_template() 1372 | post_data["table"] = "mbs" 1373 | post_data["index_position"] = 2 1374 | post_data["key_type"] = "i64" 1375 | 1376 | resp = self.http_post(post_data) 1377 | self.log.debug("get_mbs:{0}".format(resp.text)) 1378 | resp = resp.json() 1379 | mbs = [] 1380 | self.mbs_saved_claims = MbsSavedClaims() 1381 | for item in resp["rows"]: 1382 | mb = res.create_mbs(item) 1383 | if mb: 1384 | self.add_saved_claims(mb) 1385 | mbs.append(mb) 1386 | else: 1387 | self.log.warning("尚未支持的会员卡类型:{0}".format(item)) 1388 | return mbs 1389 | 1390 | def add_saved_claims(self, MBS): 1391 | if MBS.type == 'Wood': 1392 | self.mbs_saved_claims.Wood += MBS.saved_claims 1393 | if MBS.type == 'Food': 1394 | self.mbs_saved_claims.Food += MBS.saved_claims 1395 | if MBS.type == 'Gold': 1396 | self.mbs_saved_claims.Gold += MBS.saved_claims 1397 | 1398 | self.log.debug("mbs_saved_claims:{0}".format(self.mbs_saved_claims)) 1399 | 1400 | def claim_mbs(self, tools: List[MBS]): 1401 | for item in tools: 1402 | self.log.info("正在点击会员卡: {0}".format(item.show(True))) 1403 | self.consume_energy(Decimal(item.energy_consumed)) 1404 | transaction = { 1405 | "actions": [{ 1406 | "account": "farmersworld", 1407 | "name": "mbsclaim", 1408 | "authorization": [{ 1409 | "actor": self.wax_account, 1410 | "permission": "active", 1411 | }], 1412 | "data": { 1413 | "asset_id": item.asset_id, 1414 | "owner": self.wax_account, 1415 | }, 1416 | }], 1417 | } 1418 | self.wax_transact(transaction) 1419 | self.log.info("点击会员卡成功: {0}".format(item.show(more=False))) 1420 | time.sleep(cfg.req_interval) 1421 | 1422 | def scan_withdraw(self): 1423 | self.log.info("检查是否可以提现") 1424 | r = self.resoure 1425 | # 获取提现费率 1426 | withdraw_wood = 0 1427 | withdraw_food = 0 1428 | withdraw_gold = 0 1429 | config = self.get_farming_config() 1430 | withdraw_fee = config["fee"] 1431 | self.log.info(f"提现费率:{withdraw_fee}% ") 1432 | 1433 | if withdraw_fee == 5: 1434 | if r.wood > user_param.need_fww: 1435 | withdraw_wood = r.wood - user_param.need_fww 1436 | if r.gold > user_param.need_fwg: 1437 | withdraw_gold = r.gold - user_param.need_fwg 1438 | if r.food > user_param.need_fwf: 1439 | withdraw_food = r.food - user_param.need_fwf 1440 | if withdraw_food + withdraw_gold + withdraw_wood < user_param.withdraw_min: 1441 | self.log.info("提现数量太少了,下次再提") 1442 | return True 1443 | self.do_withdraw(withdraw_food, withdraw_gold, withdraw_wood, withdraw_fee) 1444 | self.log.info(f"提现:金币【{withdraw_gold}】 木头【{withdraw_wood}】 食物【{withdraw_food}】 费率【{withdraw_fee}】") 1445 | else: 1446 | self.log.info("不满足提现条件") 1447 | 1448 | return True 1449 | 1450 | def scan_resource(self): 1451 | r = self.get_resource() 1452 | self.log.info(f"金币【{r.gold}】 木头【{r.wood}】 食物【{r.food}】 能量【{r.energy}/{r.max_energy}】") 1453 | self.resoure = r 1454 | time.sleep(cfg.req_interval) 1455 | self.token = self.get_fw_balance() 1456 | self.log.info(f"FWG【{self.token.fwg}】 FWW【{self.token.fww}】 FWF【{self.token.fwf}】") 1457 | if self.resoure.energy <= user_param.min_energy: 1458 | self.log.info("能量小于配置的最小能量,开启能量补充{0}".format(self.resoure.max_energy)) 1459 | recover = min(user_param.recover_energy, self.resoure.max_energy) - self.resoure.energy 1460 | recover = (recover // Decimal(5)) * Decimal(5) 1461 | self.recover_energy(recover) 1462 | self.resoure.energy += recover 1463 | 1464 | 1465 | 1466 | def reset_before_scan(self): 1467 | self.not_operational.clear() 1468 | self.count_success_claim = 0 1469 | self.count_error_claim = 0 1470 | 1471 | # 检查正在培养的作物, 返回值:是否继续运行程序 1472 | def scan_all(self) -> int: 1473 | status = Status.Continue 1474 | try: 1475 | self.reset_before_scan() 1476 | self.log.info("开始一轮扫描") 1477 | self.scan_resource() 1478 | time.sleep(cfg.req_interval) 1479 | 1480 | if user_param.mbs: 1481 | self.scan_mbs() 1482 | time.sleep(cfg.req_interval) 1483 | if user_param.mining: 1484 | self.scan_mining() 1485 | time.sleep(cfg.req_interval) 1486 | if user_param.plant: 1487 | self.scan_crops() 1488 | time.sleep(cfg.req_interval) 1489 | # 养牛和养鸡 1490 | if user_param.chicken or user_param.cow: 1491 | self.scan_animals() 1492 | time.sleep(cfg.req_interval) 1493 | # 繁殖喂养 1494 | if user_param.breeding: 1495 | self.scan_breedings() 1496 | time.sleep(cfg.req_interval) 1497 | if user_param.withdraw: 1498 | self.scan_withdraw() 1499 | time.sleep(cfg.req_interval) 1500 | if user_param.auto_deposit: 1501 | self.scan_deposit() 1502 | time.sleep(cfg.req_interval) 1503 | if user_param.sell_corn or user_param.sell_barley or user_param.sell_milk or user_param.sell_egg: 1504 | # 卖玉米和大麦和牛奶 1505 | self.scan_nft_assets() 1506 | time.sleep(cfg.req_interval) 1507 | if user_param.build: 1508 | self.scan_buildings() 1509 | time.sleep(cfg.req_interval) 1510 | if user_param.auto_plant: 1511 | self.scan_plants() 1512 | time.sleep(cfg.req_interval) 1513 | self.log.info("结束一轮扫描") 1514 | if self.not_operational: 1515 | self.next_operate_time = min([item.next_availability for item in self.not_operational]) 1516 | self.log.info("下一次可操作时间: {0}".format(utils.show_time(self.next_operate_time))) 1517 | # 可操作时间到了,也要延后5秒再扫,以免 1518 | self.next_operate_time += timedelta(seconds=5) 1519 | else: 1520 | self.next_operate_time = datetime.max 1521 | if self.count_success_claim > 0 or self.count_error_claim > 0: 1522 | self.log.info(f"本轮操作成功数量: {self.count_success_claim} 操作失败数量: {self.count_error_claim}") 1523 | 1524 | if self.count_error_claim > 0: 1525 | self.log.info("本轮有失败操作,稍后重试") 1526 | self.next_scan_time = datetime.now() + cfg.min_scan_interval 1527 | else: 1528 | self.next_scan_time = datetime.now() + cfg.max_scan_interval 1529 | 1530 | self.next_scan_time = min(self.next_scan_time, self.next_operate_time) 1531 | 1532 | # 没有合约出错,清空错误计数器 1533 | self.count_error_transact = 0 1534 | 1535 | except TransactException as e: 1536 | # self.log.exception("智能合约调用出错") 1537 | if not e.retry: 1538 | return Status.Stop 1539 | self.count_error_transact += 1 1540 | self.log.error("合约调用异常【{0}】次".format(self.count_error_transact)) 1541 | if self.count_error_transact >= e.max_retry_times and e.max_retry_times != -1: 1542 | self.log.error("合约连续调用异常") 1543 | return Status.Stop 1544 | self.next_scan_time = datetime.now() + cfg.min_scan_interval 1545 | except CookieExpireException as e: 1546 | self.log.exception(str(e)) 1547 | self.log.error("Cookie失效,请手动重启程序并重新登录") 1548 | return Status.Stop 1549 | except StopException as e: 1550 | self.log.exception(str(e)) 1551 | self.log.error("不可恢复错误,请手动处理,然后重启程序并重新登录") 1552 | return Status.Stop 1553 | except FarmerException as e: 1554 | self.log.exception(str(e)) 1555 | self.log.error("常规错误,稍后重试") 1556 | self.next_scan_time = datetime.now() + cfg.min_scan_interval 1557 | except Exception as e: 1558 | self.log.exception(str(e)) 1559 | self.log.error("常规错误,稍后重试") 1560 | self.next_scan_time = datetime.now() + cfg.min_scan_interval 1561 | 1562 | self.log.info("下一轮扫描时间: {0}".format(utils.show_time(self.next_scan_time))) 1563 | return status 1564 | 1565 | def run_forever(self): 1566 | while True: 1567 | if datetime.now() > self.next_scan_time: 1568 | status = self.scan_all() 1569 | if status == Status.Stop: 1570 | self.close() 1571 | self.log.info("程序已停止,请检查日志后手动重启程序") 1572 | return 1 1573 | time.sleep(1) 1574 | 1575 | 1576 | def test(): 1577 | pass 1578 | 1579 | 1580 | if __name__ == '__main__': 1581 | test() 1582 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encoderlee/OpenFarmer/e8dd3e7bb6eba90a969208aef5ee05237898a92c/favicon.ico -------------------------------------------------------------------------------- /gui.pyw: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import * 2 | from PyQt6.QtCore import Qt, QThread, QObject, pyqtSignal 3 | from PyQt6 import QtGui 4 | from dialog import Ui_Dialog 5 | import logging 6 | from farmer import Farmer 7 | import logger 8 | from logger import log 9 | import yaml 10 | import sys 11 | import utils 12 | from settings import load_user_param, user_param 13 | import os 14 | 15 | version = "1.2" 16 | 17 | 18 | def resource_path(relative_path): 19 | base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) 20 | return os.path.join(base_path, relative_path) 21 | 22 | 23 | class QTextEditLogHandler(QObject, logging.Handler): 24 | signal_log = pyqtSignal(str) 25 | 26 | def emit(self, record): 27 | msg = self.format(record) 28 | self.signal_log.emit(msg) 29 | 30 | 31 | class Worker(QThread): 32 | def __init__(self, farmer: Farmer): 33 | super().__init__() 34 | self.farmer = farmer 35 | 36 | def run(self): 37 | logger.init_loger(user_param.wax_account) 38 | log.info("项目开源地址:https://github.com/lintan/OpenFarmer") 39 | log.info("WAX账号: {0}".format(user_param.wax_account)) 40 | utils.clear_orphan_webdriver() 41 | self.farmer.rpc_domain = user_param.rpc_domain 42 | self.farmer.assets_domain = user_param.assets_domain 43 | self.farmer.wax_account = user_param.wax_account 44 | if user_param.use_proxy: 45 | self.farmer.proxy = user_param.proxy 46 | log.info("use proxy: {0}".format(user_param.proxy)) 47 | self.farmer.init() 48 | self.farmer.start() 49 | log.info("开始自动化,请勿刷新浏览器,如需手工操作建议新开一个浏览器操作") 50 | return self.farmer.run_forever() 51 | 52 | 53 | class MyDialog(QDialog, Ui_Dialog): 54 | def __init__(self, parent=None): 55 | super().__init__(parent) 56 | self.user_yml = "user.yml" 57 | self.farmer = Farmer() 58 | self.setupUi(self) 59 | self.setWindowTitle("农民世界助手{0}".format(version)) 60 | self.setWindowIcon(QtGui.QIcon(resource_path("favicon.ico"))) 61 | self.setWindowFlags(Qt.WindowType.WindowMinimizeButtonHint | Qt.WindowType.WindowCloseButtonHint) 62 | self.setFixedSize(self.size()) 63 | self.button_start.clicked.connect(self.start) 64 | handler = QTextEditLogHandler() 65 | handler.signal_log.connect(self.show_log) 66 | #logging_format = logging.Formatter("[%(asctime)s][%(levelname)s][%(process)d]: %(message)s") 67 | logging_format = logging.Formatter("[%(asctime)s][%(tag)s]: %(message)s", "%Y-%m-%d %H:%M:%S") 68 | handler.setFormatter(logging_format) 69 | logging.getLogger().addHandler(handler) 70 | self.load_yaml() 71 | self.worker = Worker(self.farmer) 72 | 73 | def load_yaml(self): 74 | if len(sys.argv) == 2: 75 | self.user_yml = sys.argv[1] 76 | with open(self.user_yml, "r", encoding="utf8") as file: 77 | user: dict = yaml.load(file, Loader=yaml.FullLoader) 78 | file.close() 79 | load_user_param(user) 80 | self.update_ui(False) 81 | 82 | def update_ui(self, ui_to_user_param: bool): 83 | if ui_to_user_param: 84 | user_param.rpc_domain = self.comboBox_rpc_domain.currentText() 85 | user_param.rpc_domain_list = user_param.rpc_domain_list 86 | user_param.assets_domain_list = user_param.assets_domain_list 87 | user_param.assets_domain = user_param.assets_domain 88 | 89 | user_param.wax_account = self.edit_account.text() 90 | user_param.use_proxy = self.checkbox_proxy.isChecked() 91 | user_param.proxy = self.edit_proxy.text() 92 | user_param.build = self.checkbox_build.isChecked() 93 | user_param.mining = self.checkbox_mining.isChecked() 94 | user_param.chicken = self.checkbox_chicken.isChecked() 95 | user_param.plant = self.checkbox_plant.isChecked() 96 | user_param.cow = self.checkbox_cow.isChecked() 97 | user_param.mbs = self.checkbox_mbs.isChecked() 98 | user_param.mbs_mint = self.checkbox_mbs_mint.isChecked() 99 | user_param.recover_energy = self.spinbox_energy.value() 100 | user_param.min_energy = self.spinbox_min_energy.value() 101 | user_param.min_durability = self.spinbox_min_durability.value() 102 | # 自动提现 103 | user_param.withdraw = self.checkbox_withdraw.isChecked() 104 | user_param.need_fww = int(self.need_fww.text()) 105 | user_param.need_fwf = int(self.need_fwf.text()) 106 | user_param.need_fwg = int(self.need_fwg.text()) 107 | user_param.withdraw_min = int(self.withdraw_min.text()) 108 | # 自动充值 109 | user_param.auto_deposit = self.checkbox_auto_deposit.isChecked() 110 | user_param.fww_min = int(self.fww_min.text()) 111 | user_param.fwf_min = int(self.fwf_min.text()) 112 | user_param.fwg_min = int(self.fwg_min.text()) 113 | user_param.deposit_fww = int(self.deposit_fww.text()) 114 | user_param.deposit_fwf = int(self.deposit_fwf.text()) 115 | user_param.deposit_fwg = int(self.deposit_fwg.text()) 116 | # 卖玉米 117 | user_param.sell_corn = self.checkbox_sell_corn.isChecked() 118 | user_param.remaining_corn_num = int(self.remaining_corn_num.text()) 119 | # 卖大麦 120 | user_param.sell_barley = self.checkbox_sell_barley.isChecked() 121 | user_param.remaining_barley_num = int(self.remaining_barley_num.text()) 122 | # 卖牛奶 123 | user_param.sell_milk = self.checkbox_sell_milk.isChecked() 124 | user_param.remaining_milk_num = int(self.remaining_milk_num.text()) 125 | # 卖鸡蛋 126 | user_param.sell_egg = self.checkbox_sell_egg.isChecked() 127 | user_param.remaining_egg_num = int(self.remaining_egg_num.text()) 128 | # 自动播种 129 | user_param.auto_plant = self.checkbox_auto_plant.isChecked() 130 | user_param.barleyseed_num = int(self.barleyseed_num.text()) 131 | user_param.cornseed_num = int(self.cornseed_num.text()) 132 | # 自动购买 133 | user_param.buy_food = self.checkbox_buy_food.isChecked() 134 | user_param.buy_barley_seed = self.checkbox_buy_barley_seed.isChecked() 135 | user_param.buy_corn_seed = self.checkbox_buy_corn_seed.isChecked() 136 | user_param.buy_food_num = int(self.buy_food_num.text()) 137 | # 自动购买 138 | user_param.breeding = self.checkbox_breeding.isChecked() 139 | 140 | else: 141 | self.comboBox_rpc_domain.setCurrentText(user_param.rpc_domain) 142 | self.comboBox_assets_domain.setCurrentText(user_param.assets_domain) 143 | 144 | self.edit_account.setText(user_param.wax_account) 145 | self.checkbox_proxy.setChecked(user_param.use_proxy) 146 | self.edit_proxy.setText(user_param.proxy) 147 | self.checkbox_build.setChecked(user_param.build) 148 | self.checkbox_mining.setChecked(user_param.mining) 149 | self.checkbox_chicken.setChecked(user_param.chicken) 150 | self.checkbox_plant.setChecked(user_param.plant) 151 | self.checkbox_cow.setChecked(user_param.cow) 152 | self.checkbox_mbs.setChecked(user_param.mbs) 153 | self.checkbox_mbs_mint.setChecked(user_param.mbs_mint) 154 | self.spinbox_energy.setValue(user_param.recover_energy) 155 | self.spinbox_min_energy.setValue(user_param.min_energy) 156 | self.spinbox_min_durability.setValue(user_param.min_durability) 157 | # 自动提现 158 | self.checkbox_withdraw.setChecked(user_param.withdraw) 159 | self.need_fww.setText(str(user_param.need_fww)) 160 | self.need_fwf.setText(str(user_param.need_fwf)) 161 | self.need_fwg.setText(str(user_param.need_fwg)) 162 | self.withdraw_min.setText(str(user_param.withdraw_min)) 163 | # 自动充值 164 | self.checkbox_auto_deposit.setChecked(user_param.auto_deposit) 165 | self.fww_min.setText(str(user_param.fww_min)) 166 | self.fwf_min.setText(str(user_param.fwf_min)) 167 | self.fwg_min.setText(str(user_param.fwg_min)) 168 | self.deposit_fww.setText(str(user_param.deposit_fww)) 169 | self.deposit_fwf.setText(str(user_param.deposit_fwf)) 170 | self.deposit_fwg.setText(str(user_param.deposit_fwg)) 171 | # 卖玉米 172 | self.checkbox_sell_corn.setChecked(user_param.sell_corn) 173 | self.remaining_corn_num.setText(str(user_param.remaining_corn_num)) 174 | # 卖大麦 175 | self.checkbox_sell_barley.setChecked(user_param.sell_barley) 176 | self.remaining_barley_num.setText(str(user_param.remaining_barley_num)) 177 | # 卖牛奶 178 | self.checkbox_sell_milk.setChecked(user_param.sell_milk) 179 | self.remaining_milk_num.setText(str(user_param.remaining_milk_num)) 180 | # 卖鸡蛋 181 | self.checkbox_sell_egg.setChecked(user_param.sell_egg) 182 | self.remaining_egg_num.setText(str(user_param.remaining_egg_num)) 183 | # 自动播种 184 | self.checkbox_auto_plant.setChecked(user_param.auto_plant) 185 | self.barleyseed_num.setText(str(user_param.barleyseed_num)) 186 | self.cornseed_num.setText(str(user_param.cornseed_num)) 187 | # 自动购买 188 | self.checkbox_buy_food.setChecked(user_param.buy_food) 189 | self.checkbox_buy_barley_seed.setChecked(user_param.buy_barley_seed) 190 | self.checkbox_buy_corn_seed.setChecked(user_param.buy_corn_seed) 191 | self.buy_food_num.setText(str(user_param.buy_food_num)) 192 | # 繁殖 193 | self.checkbox_breeding.setChecked(user_param.breeding) 194 | 195 | def setEnabled(self, status: bool): 196 | self.comboBox_rpc_domain.setEnabled(status) 197 | self.comboBox_assets_domain.setEnabled(status) 198 | 199 | self.checkbox_cow.setEnabled(status) 200 | self.checkbox_build.setEnabled(status) 201 | self.checkbox_plant.setEnabled(status) 202 | self.checkbox_mining.setEnabled(status) 203 | self.checkbox_mbs.setEnabled(status) 204 | self.checkbox_mbs_mint.setEnabled(status) 205 | self.checkbox_proxy.setEnabled(status) 206 | self.checkbox_chicken.setEnabled(status) 207 | self.edit_proxy.setEnabled(status) 208 | self.edit_account.setEnabled(status) 209 | self.spinbox_energy.setEnabled(status) 210 | self.button_start.setEnabled(status) 211 | 212 | self.spinbox_min_energy.setEnabled(status) 213 | self.spinbox_min_durability.setEnabled(status) 214 | # 自动提现 215 | self.checkbox_withdraw.setEnabled(status) 216 | self.need_fww.setEnabled(status) 217 | self.need_fwf.setEnabled(status) 218 | self.need_fwg.setEnabled(status) 219 | self.withdraw_min.setEnabled(status) 220 | # 自动充值 221 | self.checkbox_auto_deposit.setEnabled(status) 222 | self.fww_min.setEnabled(status) 223 | self.fwf_min.setEnabled(status) 224 | self.fwg_min.setEnabled(status) 225 | self.deposit_fww.setEnabled(status) 226 | self.deposit_fwf.setEnabled(status) 227 | self.deposit_fwg.setEnabled(status) 228 | # 卖玉米 229 | self.checkbox_sell_corn.setEnabled(status) 230 | self.remaining_corn_num.setEnabled(status) 231 | # 卖大麦 232 | self.checkbox_sell_barley.setEnabled(status) 233 | self.remaining_barley_num.setEnabled(status) 234 | # 卖牛奶 235 | self.checkbox_sell_milk.setEnabled(status) 236 | self.remaining_milk_num.setEnabled(status) 237 | # 卖鸡蛋 238 | self.checkbox_sell_egg.setEnabled(status) 239 | self.remaining_egg_num.setEnabled(status) 240 | # 自动播种 241 | self.checkbox_auto_plant.setEnabled(status) 242 | self.barleyseed_num.setEnabled(status) 243 | self.cornseed_num.setEnabled(status) 244 | # 自动购买 245 | self.checkbox_buy_food.setEnabled(status) 246 | self.checkbox_buy_barley_seed.setEnabled(status) 247 | self.checkbox_buy_corn_seed.setEnabled(status) 248 | self.buy_food_num.setEnabled(status) 249 | # 繁殖 250 | self.checkbox_breeding.setEnabled(status) 251 | for i in range(1, 27): 252 | exec('self.label_{}.setEnabled(status)'.format(i)) 253 | 254 | def start(self): 255 | self.setEnabled(False) 256 | self.setWindowTitle("农民世界助手{0}【{1}】".format(version, self.edit_account.text())) 257 | self.update_ui(True) 258 | self.worker.start() 259 | 260 | def show_log(self, line: str): 261 | self.plain_text_edit.appendPlainText(line) 262 | 263 | def stop(self): 264 | self.update_ui(True) 265 | self.setEnabled(True) 266 | with open(self.user_yml, "w") as file: 267 | yaml.dump(user_param.to_dict(), file, default_flow_style=False, sort_keys=False) 268 | self.plain_text_edit.appendPlainText("稍等,程序正在退出...") 269 | self.repaint() 270 | self.farmer.close() 271 | self.plain_text_edit.appendPlainText("程序已退出") 272 | 273 | def closeEvent(self, event: QtGui.QCloseEvent): 274 | self.stop() 275 | event.accept() 276 | 277 | 278 | def main(): 279 | app = QApplication(sys.argv) 280 | ui = MyDialog() 281 | ui.show() 282 | sys.exit(app.exec()) 283 | 284 | 285 | if __name__ == '__main__': 286 | main() 287 | -------------------------------------------------------------------------------- /gui.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | block_cipher = None 5 | 6 | add_files = [ 7 | ('./chromedriver.exe','./'), 8 | ('./inject.js','./'), 9 | ('./waxjs.js','./'), 10 | ('./user.yml','./'), 11 | ('./favicon.ico','./'), 12 | ] 13 | a = Analysis(['gui.pyw'], 14 | pathex=[], 15 | binaries=[], 16 | datas=add_files, 17 | hiddenimports=[], 18 | hookspath=[], 19 | hooksconfig={}, 20 | runtime_hooks=[], 21 | excludes=[], 22 | win_no_prefer_redirects=False, 23 | win_private_assemblies=False, 24 | cipher=block_cipher, 25 | noarchive=False) 26 | pyz = PYZ(a.pure, a.zipped_data, 27 | cipher=block_cipher) 28 | 29 | exe = EXE(pyz, 30 | a.scripts, 31 | [], 32 | exclude_binaries=True, 33 | name='gui', 34 | debug=False, 35 | bootloader_ignore_signals=False, 36 | strip=False, 37 | upx=True, 38 | console=False, 39 | disable_windowed_traceback=False, 40 | target_arch=None, 41 | codesign_identity=None, 42 | entitlements_file=None , icon='favicon.ico') 43 | coll = COLLECT(exe, 44 | a.binaries, 45 | a.zipfiles, 46 | a.datas, 47 | strip=False, 48 | upx=True, 49 | upx_exclude=[], 50 | name='gui') 51 | -------------------------------------------------------------------------------- /inject.js: -------------------------------------------------------------------------------- 1 | //window.mywax = new waxjs.WaxJS({rpcEndpoint: 'https://wax.dapplica.io'}); 2 | 3 | window.sleep = function sleep(ms) { 4 | return new Promise(resolve => setTimeout(resolve, ms)); 5 | } 6 | 7 | window.wax_login = async function(){ 8 | try { 9 | const auto_login = await window.mywax.isAutoLoginAvailable(); 10 | if (!auto_login) 11 | window.mywax.login(); 12 | return [true,"ok"]; 13 | } catch (e){ 14 | return [false, e.message]; 15 | } 16 | } 17 | 18 | window.wax_transact = async function(transaction){ 19 | try { 20 | const auto_login = await window.mywax.isAutoLoginAvailable(); 21 | if (!auto_login){ 22 | await window.mywax.login(); 23 | await window.sleep(3000); 24 | } 25 | console.log(transaction) 26 | const result = await window.mywax.api.transact(transaction, { 27 | blocksBehind: 3, 28 | expireSeconds: 90, 29 | }); 30 | return [true, result]; 31 | } catch (e){ 32 | return [false, e.message]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /install_depends.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.system("python -m pip install -r requirements.txt -i https://pypi.douban.com/simple/") 4 | -------------------------------------------------------------------------------- /logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from logging import handlers 4 | import os 5 | from settings import cfg 6 | 7 | _log = logging.getLogger(__name__) 8 | _log.setLevel(logging.INFO) 9 | log = logging.LoggerAdapter(_log, {"tag": "global"}) 10 | 11 | 12 | def init_loger(loger_name: str): 13 | handler = logging.StreamHandler(sys.stdout) 14 | #logging_format = logging.Formatter("[%(asctime)s][%(levelname)s][%(process)d][%(tag)s]: %(message)s") 15 | logging_format = logging.Formatter("[%(asctime)s][%(tag)s]: %(message)s","%Y-%m-%d %H:%M:%S") 16 | handler.setFormatter(logging_format) 17 | logging.getLogger().addHandler(handler) 18 | if not os.path.exists(cfg.path_logs): 19 | os.makedirs(cfg.path_logs) 20 | log_file_name = "{0}.log".format(loger_name) 21 | log_file_path = os.path.join(cfg.path_logs, log_file_name) 22 | handler = logging.handlers.TimedRotatingFileHandler(log_file_path, when="midnight", encoding='UTF-8', 23 | backupCount=30) 24 | handler.suffix = "%Y-%m-%d" 25 | handler.setFormatter(logging_format) 26 | logging.getLogger().addHandler(handler) 27 | 28 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from farmer import Farmer 3 | import logger 4 | from logger import log 5 | import yaml 6 | import sys 7 | import utils 8 | from settings import load_user_param, user_param 9 | 10 | def run(config_file: str): 11 | with open(config_file, "r", encoding="utf8") as file: 12 | user: dict = yaml.load(file, Loader=yaml.FullLoader) 13 | file.close() 14 | load_user_param(user) 15 | logger.init_loger(user_param.wax_account) 16 | log.info("项目开源地址:https://github.com/lintan/OpenFarmer") 17 | log.info("WAX账号: {0}".format(user_param.wax_account)) 18 | utils.clear_orphan_webdriver() 19 | farmer = Farmer() 20 | farmer.wax_account = user_param.wax_account 21 | if user_param.use_proxy: 22 | farmer.proxy = user_param.proxy 23 | log.info("use proxy: {0}".format(user_param.proxy)) 24 | farmer.init() 25 | farmer.start() 26 | log.info("开始自动化,请勿刷新浏览器,如需手工操作建议新开一个浏览器操作") 27 | return farmer.run_forever() 28 | 29 | 30 | def main(): 31 | try: 32 | user_yml = "user.yml" 33 | if len(sys.argv) == 2: 34 | user_yml = sys.argv[1] 35 | run(user_yml) 36 | except Exception: 37 | log.exception("start error") 38 | input() 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | block_cipher = None 5 | 6 | add_files = [ 7 | ('./chromedriver.exe','./'), 8 | ('./inject.js','./'), 9 | ('./waxjs.js','./'), 10 | ('./user.yml','./'), 11 | ('./favicon.ico','./'), 12 | ] 13 | a = Analysis(['main.py'], 14 | pathex=[], 15 | binaries=[], 16 | datas=add_files, 17 | hiddenimports=[], 18 | hookspath=[], 19 | hooksconfig={}, 20 | runtime_hooks=[], 21 | excludes=[], 22 | win_no_prefer_redirects=False, 23 | win_private_assemblies=False, 24 | cipher=block_cipher, 25 | noarchive=False) 26 | pyz = PYZ(a.pure, a.zipped_data, 27 | cipher=block_cipher) 28 | 29 | exe = EXE(pyz, 30 | a.scripts, 31 | [], 32 | exclude_binaries=True, 33 | name='main', 34 | debug=False, 35 | bootloader_ignore_signals=False, 36 | strip=False, 37 | upx=True, 38 | console=True, 39 | disable_windowed_traceback=False, 40 | target_arch=None, 41 | codesign_identity=None, 42 | entitlements_file=None , icon='favicon.ico') 43 | coll = COLLECT(exe, 44 | a.binaries, 45 | a.zipfiles, 46 | a.datas, 47 | strip=False, 48 | upx=True, 49 | upx_exclude=[], 50 | name='main') 51 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | selenium 3 | pyyaml 4 | tenacity 5 | psutil-wheels 6 | pyqt6 -------------------------------------------------------------------------------- /res.py: -------------------------------------------------------------------------------- 1 | # 游戏里的各种数据结构 2 | from decimal import Decimal 3 | from dataclasses import dataclass 4 | from datetime import datetime, timedelta 5 | from typing import List, ClassVar, Dict 6 | import utils 7 | 8 | farming_table = {} 9 | 10 | 11 | # nft template_id 12 | class NFT: 13 | Barley: int = 318606 # 大麦 14 | Corn: int = 318607 # 玉米 15 | Chicken: int = 298614 # 大鸡 16 | Chick: int = 298613 # 小鸡 17 | ChickenEgg: int = 298612 # 鸡蛋 18 | BabyCalf: int = 298597 # 小牛犊 19 | Calf: int = 298598 # 小牛 20 | FeMaleCalf: int = 298599 # 母小牛 21 | MaleCalf: int = 298600 # 公小牛 22 | Bull: int = 298611 # 公牛 23 | DairyCow: int = 298607 # 奶牛 24 | CornSeed: int = 298596 # 玉米种子 25 | BarleySeed: int = 298595 # 大麦种子 26 | Milk: int = 298593 # 牛奶 27 | 28 | 29 | # 金、木、食物、能量 30 | @dataclass(init=False) 31 | class Resoure: 32 | energy: Decimal = None 33 | max_energy: Decimal = None 34 | gold: Decimal = None 35 | wood: Decimal = None 36 | food: Decimal = None 37 | 38 | 39 | @dataclass(init=False) 40 | class Token: 41 | fwg: Decimal = None 42 | fww: Decimal = None 43 | fwf: Decimal = None 44 | 45 | 46 | @dataclass(init=False) 47 | class MbsSavedClaims: 48 | Wood: int = 0 49 | Food: int = 0 50 | Gold: int = 0 51 | 52 | 53 | # 可操作的作物 54 | @dataclass(init=False) 55 | class Farming: 56 | asset_id: str = None 57 | name: str = None 58 | template_id: int = None 59 | next_availability: datetime = None 60 | 61 | def show(self, more=True) -> str: 62 | if more: 63 | return f"[{self.name}] [{self.asset_id}] [可操作时间:{utils.show_time(self.next_availability)}]" 64 | else: 65 | return f"[{self.name}] [{self.asset_id}]" 66 | 67 | 68 | # ==== Food ===== 69 | # 牛奶 70 | @dataclass(init=False) 71 | class Milk(Farming): 72 | name: str = "Milk" 73 | template_id: int = 298593 74 | 75 | 76 | # 玉米 77 | @dataclass(init=False) 78 | class Corn(Farming): 79 | name: str = "Corn" 80 | template_id: int = 318607 81 | golds_cost: int = 82 82 | 83 | 84 | # 大麦 85 | @dataclass(init=False) 86 | class Barley(Farming): 87 | name: str = "Barley" 88 | template_id: int = 318606 89 | golds_cost: int = 55 90 | 91 | 92 | supported_foods = [Milk, Corn, Barley] 93 | farming_table.update({cls.template_id: cls for cls in supported_foods}) 94 | 95 | 96 | # ==== Food ===== 97 | 98 | # ################### Animal ###################### 99 | 100 | # 动物 101 | @dataclass(init=False) 102 | class Animal(Farming): 103 | # 能量消耗 104 | energy_consumed: int = None 105 | # 当前喂养次数 106 | times_claimed: int = None 107 | # 最大喂养次数 108 | required_claims: int = None 109 | # 最后喂养时间 110 | last_claimed: datetime = None 111 | # 喂养时间列表 112 | day_claims_at: List[datetime] = None 113 | # 间隔 114 | charge_time: timedelta = None 115 | # 24小时喂养次数 116 | daily_claim_limit: int = None 117 | # 消耗的nft 118 | consumed_card: int = None 119 | # 所属建筑 120 | required_building: int = None 121 | # 繁殖 122 | bearer_id: int = None 123 | partner_id: int = None 124 | 125 | def show(self, more=True, breeding=False) -> str: 126 | if more: 127 | if len(self.day_claims_at) >= self.daily_claim_limit: 128 | next_op_time = self.day_claims_at[0] + timedelta(hours=24) 129 | self.next_availability = max(self.next_availability, next_op_time) 130 | if not breeding: 131 | text = f"[{self.name}] [{self.asset_id}][24小时喂养次数{len(self.day_claims_at)}/{self.daily_claim_limit}] [喂养次数{self.times_claimed}/{self.required_claims}] [可操作时间:{utils.show_time(self.next_availability)}] " 132 | else: 133 | text = f"[{self.name}繁殖] [{self.bearer_id}][24小时喂养次数{len(self.day_claims_at)}/{self.daily_claim_limit}] [喂养次数{self.times_claimed}/{self.required_claims}] [可操作时间:{utils.show_time(self.next_availability)}] " 134 | return text 135 | else: 136 | return f"[{self.name}] [{self.asset_id}]" 137 | 138 | 139 | # 大鸡 140 | @dataclass(init=False) 141 | class Chicken(Animal): 142 | name: str = "Chicken" 143 | template_id: int = 298614 144 | 145 | 146 | # 小鸡 147 | @dataclass(init=False) 148 | class Chick(Animal): 149 | name: str = "Chick" 150 | template_id: int = 298613 151 | 152 | 153 | # 小鸡 154 | @dataclass(init=False) 155 | class ChickenEgg(Animal): 156 | name: str = "ChickenEgg" 157 | template_id: int = 298612 158 | 159 | 160 | # 小牛犊 161 | @dataclass(init=False) 162 | class BabyCalf(Animal): 163 | name: str = "BabyCalf" 164 | template_id: int = 298597 165 | 166 | 167 | # 小牛 168 | @dataclass(init=False) 169 | class Calf(Animal): 170 | name: str = "Calf" 171 | template_id: int = 298598 172 | 173 | 174 | # 母小牛 175 | @dataclass(init=False) 176 | class FeMaleCalf(Animal): 177 | name: str = "FeMaleCalf" 178 | template_id: int = 298599 179 | 180 | 181 | # 公小牛 182 | @dataclass(init=False) 183 | class MaleCalf(Animal): 184 | name: str = "MaleCalf" 185 | template_id: int = 298600 186 | 187 | 188 | # 公牛 189 | @dataclass(init=False) 190 | class Bull(Animal): 191 | name: str = "Bull" 192 | template_id: int = 298611 193 | 194 | 195 | # 奶牛 196 | @dataclass(init=False) 197 | class DairyCow(Animal): 198 | name: str = "Dairy Cow" 199 | template_id: int = 298607 200 | 201 | 202 | supported_animals = [Chicken, Chick, ChickenEgg, BabyCalf, Calf, FeMaleCalf, MaleCalf, DairyCow] 203 | 204 | farming_table.update({cls.template_id: cls for cls in supported_animals}) 205 | 206 | 207 | def init_animal_config(rows: List[dict]): 208 | for item in rows: 209 | animal_class: Animal = farming_table.get(item["template_id"], None) 210 | if animal_class: 211 | animal_class.name = item["name"] 212 | animal_class.energy_consumed = item["energy_consumed"] 213 | animal_class.charge_time = timedelta(seconds=item["charge_time"]) 214 | animal_class.required_claims = item["required_claims"] 215 | animal_class.daily_claim_limit = item["daily_claim_limit"] 216 | animal_class.consumed_card = item["consumed_card"] 217 | animal_class.required_building = item["required_building"] 218 | 219 | 220 | # 动物-从http返回的json数据构造对象 221 | def create_animal(item: dict, breeding=False) -> Animal: 222 | animal_class = farming_table.get(item["template_id"], None) 223 | if not animal_class: 224 | return None 225 | animal = animal_class() 226 | animal.day_claims_at = [datetime.fromtimestamp(item) for item in item["day_claims_at"]] 227 | animal.name = item["name"] 228 | animal.template_id = item["template_id"] 229 | animal.times_claimed = item.get("times_claimed", None) 230 | animal.last_claimed = datetime.fromtimestamp(item["last_claimed"]) 231 | animal.next_availability = datetime.fromtimestamp(item["next_availability"]) 232 | if not breeding: 233 | animal.asset_id = item["asset_id"] 234 | else: 235 | animal.required_claims = 9 # 繁殖目前就只有奶牛,先写死 236 | animal.daily_claim_limit = 3 # 繁殖目前就只有奶牛,先写死 237 | animal.consumed_card = 318607 # 繁殖目前就只有奶牛,先写死 238 | animal.bearer_id = item["bearer_id"] 239 | animal.partner_id = item["partner_id"] 240 | 241 | return animal 242 | 243 | 244 | # 动物-从http返回的json数据构造对象 245 | def create_breeding(item: dict) -> Animal: 246 | animal_class = farming_table.get(item["template_id"], None) 247 | if not animal_class: 248 | return None 249 | animal = animal_class() 250 | animal.day_claims_at = [datetime.fromtimestamp(item) for item in item["day_claims_at"]] 251 | animal.asset_id = item["asset_id"] 252 | animal.name = item["name"] 253 | animal.template_id = item["template_id"] 254 | animal.times_claimed = item.get("times_claimed", None) 255 | animal.last_claimed = datetime.fromtimestamp(item["last_claimed"]) 256 | animal.next_availability = datetime.fromtimestamp(item["next_availability"]) 257 | return animal 258 | 259 | 260 | ####################################################### Animal ####################################################### 261 | 262 | ####################################################### Crop ####################################################### 263 | 264 | # 农作物,大麦,玉米 265 | @dataclass(init=False) 266 | class Crop(Farming): 267 | times_claimed: int = None 268 | last_claimed: datetime = None 269 | 270 | # 最大耕作次数 271 | required_claims: int = None 272 | # 能量消耗 273 | energy_consumed: int = None 274 | # 浇水间隔 275 | charge_time: timedelta = None 276 | 277 | def show(self, more=True) -> str: 278 | if more: 279 | return f"[{self.name}] [{self.asset_id}] [耕作次数{self.times_claimed}/{self.required_claims}] [可操作时间:{utils.show_time(self.next_availability)}]" 280 | else: 281 | return f"[{self.name}] [{self.asset_id}]" 282 | 283 | 284 | # 大麦种子 285 | @dataclass(init=False) 286 | class BarleySeed(Crop): 287 | name: str = "Barley Seed" 288 | template_id: int = 298595 289 | golds_cost: int = 50 290 | 291 | 292 | # 玉米种子 293 | @dataclass(init=False) 294 | class CornSeed(Crop): 295 | name: str = "Corn Seed" 296 | template_id: int = 298596 297 | golds_cost: int = 75 298 | 299 | 300 | supported_crops = [BarleySeed, CornSeed] 301 | 302 | farming_table.update({cls.template_id: cls for cls in supported_crops}) 303 | 304 | 305 | def init_crop_config(rows: List[dict]): 306 | for item in rows: 307 | crop_class: Crop = farming_table.get(item["template_id"], None) 308 | if crop_class: 309 | crop_class.name = item["name"] 310 | crop_class.charge_time = timedelta(seconds=item["charge_time"]) 311 | crop_class.energy_consumed = item["energy_consumed"] 312 | crop_class.required_claims = item["required_claims"] 313 | 314 | 315 | # 从json构造农作物对象 316 | def create_crop(item: dict) -> Crop: 317 | crop_class = farming_table.get(item["template_id"], None) 318 | if not crop_class: 319 | return None 320 | crop = crop_class() 321 | crop.asset_id = item["asset_id"] 322 | crop.name = item["name"] 323 | crop.times_claimed = item.get("times_claimed", None) 324 | crop.last_claimed = datetime.fromtimestamp(item["last_claimed"]) 325 | crop.next_availability = datetime.fromtimestamp(item["next_availability"]) 326 | return crop 327 | 328 | 329 | ####################################################### Tool ####################################################### 330 | 331 | # 工具 332 | @dataclass(init=False) 333 | class Tool(Farming): 334 | # 当前耐久 335 | current_durability: Decimal = None 336 | # 最大耐久 337 | durability: Decimal = None 338 | 339 | # 产出资源类型 340 | mining_type: str = None 341 | # 挖矿间隔 342 | charge_time: timedelta = None 343 | # 能量消耗 344 | energy_consumed: int = None 345 | # 耐久消耗 346 | durability_consumed: int = None 347 | 348 | def show(self, more=True) -> str: 349 | if more: 350 | return f"[{self.name}] [{self.asset_id}] [耐久度{self.current_durability}/{self.durability}] [可操作时间:{utils.show_time(self.next_availability)}]" 351 | else: 352 | return f"[{self.name}] [{self.asset_id}]" 353 | 354 | 355 | # 斧头 356 | @dataclass(init=False) 357 | class Axe(Tool): 358 | name: str = "Axe" 359 | template_id: int = 203881 360 | 361 | 362 | # 石斧 363 | @dataclass(init=False) 364 | class StoneAxe(Tool): 365 | name: str = "Stone Axe" 366 | template_id: int = 260763 367 | 368 | 369 | # 古代石斧 370 | @dataclass(init=False) 371 | class AncientStoneAxe(Tool): 372 | name: str = "Ancient Stone Axe" 373 | template_id: int = 378691 374 | 375 | 376 | # 锯子 377 | @dataclass(init=False) 378 | class Saw(Tool): 379 | name: str = "Saw" 380 | template_id: int = 203883 381 | 382 | 383 | # 电锯 384 | @dataclass(init=False) 385 | class Chainsaw(Tool): 386 | name: str = "Chainsaw" 387 | template_id: int = 203886 388 | 389 | 390 | # 钓鱼竿 391 | @dataclass(init=False) 392 | class FishingRod(Tool): 393 | name: str = "Fishing Rod" 394 | template_id: int = 203887 395 | 396 | 397 | # 渔网 398 | @dataclass(init=False) 399 | class FishingNet(Tool): 400 | name: str = "Fishing Net" 401 | template_id: int = 203888 402 | 403 | 404 | # 渔船 405 | @dataclass(init=False) 406 | class FishingBoat(Tool): 407 | name: str = "Fishing Boat" 408 | template_id: int = 203889 409 | 410 | 411 | # 挖掘机 412 | @dataclass(init=False) 413 | class MiningExcavator(Tool): 414 | name: str = "Mining Excavator" 415 | template_id: int = 203891 416 | 417 | 418 | supported_tools = [Axe, StoneAxe, AncientStoneAxe, Saw, Chainsaw, FishingRod, FishingNet, FishingBoat, MiningExcavator] 419 | 420 | farming_table.update({cls.template_id: cls for cls in supported_tools}) 421 | 422 | 423 | def init_tool_config(rows: List[dict]): 424 | for item in rows: 425 | tool_class = farming_table.get(item["template_id"], None) 426 | if tool_class: 427 | tool_class.mining_type = item["type"] 428 | tool_class.charge_time = timedelta(seconds=item["charged_time"]) 429 | tool_class.energy_consumed = item["energy_consumed"] 430 | tool_class.durability_consumed = item["durability_consumed"] 431 | 432 | 433 | # 从json构造工具对象 434 | def create_tool(item: dict) -> Tool: 435 | tool_class = farming_table.get(item["template_id"], None) 436 | if not tool_class: 437 | return None 438 | tool = tool_class() 439 | tool.asset_id = item["asset_id"] 440 | tool.next_availability = datetime.fromtimestamp(item["next_availability"]) 441 | tool.current_durability = item["current_durability"] 442 | tool.durability = item["durability"] 443 | return tool 444 | 445 | 446 | ####################################################### Tool ####################################################### 447 | 448 | ####################################################### MBS ####################################################### 449 | 450 | # 会员卡 451 | @dataclass(init=False) 452 | class MBS(Farming): 453 | energy_consumed: int = 100 454 | 455 | def __init__(self, template_id, name, type, saved_claims): 456 | self.name = name 457 | self.template_id = template_id 458 | self.type = type 459 | self.saved_claims = saved_claims 460 | 461 | def show(self, more=True) -> str: 462 | if more: 463 | return f"[{self.name}] [类型:{self.type}] [asset_id:{self.asset_id}] [可操作时间:{utils.show_time(self.next_availability)}]" 464 | else: 465 | return f"[{self.name}] [类型:{self.type}]" 466 | 467 | 468 | mbs_table: Dict[int, MBS] = {} 469 | 470 | 471 | def init_mbs_config(rows: List[dict]): 472 | for item in rows: 473 | mbs = MBS(item["template_id"], item["name"], item["type"], item["saved_claims"]) 474 | mbs_table[item["template_id"]] = mbs 475 | 476 | 477 | # 从json构造mbs对象 478 | def create_mbs(item: dict) -> MBS: 479 | mbs_class = mbs_table.get(item["template_id"], None) 480 | if not mbs_class: 481 | return None 482 | mbs = MBS(mbs_class.template_id, mbs_class.name, mbs_class.type, mbs_class.saved_claims) 483 | mbs.asset_id = item["asset_id"] 484 | mbs.next_availability = datetime.fromtimestamp(item["next_availability"]) 485 | return mbs 486 | 487 | 488 | ####################################################### MBS ####################################################### 489 | 490 | 491 | # 建筑物 492 | @dataclass(init=False) 493 | class Building(Farming): 494 | # 能量消耗 牛棚300,鸡舍250,田200 495 | energy_consumed: int = 300 496 | times_claimed: int = None 497 | last_claimed: datetime = None 498 | is_ready: int = None 499 | slots_used: int = None 500 | num_slots: int = None 501 | 502 | 503 | # NFT资产,可以是小麦,小麦种子,牛奶等 504 | @dataclass(init=False) 505 | class Asset: 506 | asset_id: str 507 | name: str 508 | is_transferable: bool 509 | is_burnable: bool 510 | schema_name: str 511 | template_id: str 512 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import timedelta 3 | 4 | 5 | @dataclass 6 | class Settings: 7 | path_logs: str 8 | chrome_data_dir: str 9 | url_db: str = None 10 | # 发送http请求的间隔 11 | req_interval = 3 12 | # 发送合约请求的间隔(在http请求的间隔基础上再加几秒) 13 | transact_interval = 1 14 | # 每小时至少扫描一次,即使没有可用的作物,这样可以处理上次扫码后新种的作物 15 | max_scan_interval = timedelta(minutes=15) 16 | # 每次扫描至少间隔10秒,哪怕是出错重扫 17 | min_scan_interval = timedelta(seconds=10) 18 | 19 | 20 | # 用户配置参数 21 | class user_param: 22 | rpc_domain_list: list = [] 23 | rpc_domain: str = None 24 | assets_domain: str = None 25 | assets_domain_list: list = [] 26 | 27 | wax_account: str = None 28 | use_proxy: bool = True 29 | proxy: str = None 30 | 31 | build: bool = True 32 | mining: bool = True 33 | chicken: bool = True 34 | plant: bool = True 35 | cow: bool = True 36 | mbs: bool = True 37 | mbs_mint: bool = False 38 | # 能量不够的时候,就去恢复那么多能量,但不超过最大能量 39 | recover_energy: int = 500 40 | 41 | withdraw: bool = True 42 | auto_deposit: bool = True 43 | sell_corn: bool = True 44 | sell_barley: bool = True 45 | sell_milk: bool = True 46 | sell_egg: bool = True 47 | auto_plant: bool = True 48 | min_energy: int = 50 49 | 50 | on_server: bool = False 51 | 52 | # 账号中剩余多少材料不提现 53 | need_fww: int = 200 54 | need_fwf: int = 200 55 | need_fwg: int = 200 56 | # 最少提现数量,3种材料总和 57 | withdraw_min: int = 200 58 | 59 | remaining_corn_num: int = 0 60 | remaining_barley_num: int = 0 61 | remaining_milk_num: int = 0 62 | remaining_egg_num: int = 0 63 | 64 | barleyseed_num: int = 0 65 | cornseed_num: int = 0 66 | 67 | fww_min: int = 0 68 | deposit_fww: int = 0 69 | fwf_min: int = 0 70 | deposit_fwf: int = 0 71 | fwg_min: int = 0 72 | deposit_fwg: int = 0 73 | 74 | min_durability: int = 0 75 | 76 | # 自动买食物 77 | buy_food: bool = False 78 | buy_food_num: int = 0 79 | # 自动买大麦种子 80 | buy_barley_seed: bool = False 81 | # 自动买玉米种子 82 | buy_corn_seed: bool = False 83 | breeding: bool = False 84 | 85 | @staticmethod 86 | def to_dict(): 87 | return { 88 | "rpc_domain_list": user_param.rpc_domain_list, 89 | "rpc_domain": user_param.rpc_domain, 90 | "assets_domain_list": user_param.assets_domain_list, 91 | "assets_domain": user_param.assets_domain, 92 | "wax_account": user_param.wax_account, 93 | "use_proxy": user_param.use_proxy, 94 | "proxy": user_param.proxy, 95 | "build": user_param.build, 96 | "mining": user_param.mining, 97 | "chicken": user_param.chicken, 98 | "plant": user_param.plant, 99 | "cow": user_param.cow, 100 | "mbs": user_param.mbs, 101 | "mbs_mint": user_param.mbs_mint, 102 | "recover_energy": user_param.recover_energy, 103 | "withdraw": user_param.withdraw, 104 | "auto_deposit": user_param.auto_deposit, 105 | "sell_corn": user_param.sell_corn, 106 | "sell_barley": user_param.sell_barley, 107 | "sell_milk": user_param.sell_milk, 108 | "sell_egg": user_param.sell_egg, 109 | "auto_plant": user_param.auto_plant, 110 | "min_energy": user_param.min_energy, 111 | "on_server": user_param.on_server, 112 | "need_fww": user_param.need_fww, 113 | "need_fwf": user_param.need_fwf, 114 | "need_fwg": user_param.need_fwg, 115 | "withdraw_min": user_param.withdraw_min, 116 | "remaining_corn_num": user_param.remaining_corn_num, 117 | "remaining_barley_num": user_param.remaining_barley_num, 118 | "remaining_milk_num": user_param.remaining_milk_num, 119 | "remaining_egg_num": user_param.remaining_egg_num, 120 | "barleyseed_num": user_param.barleyseed_num, 121 | "cornseed_num": user_param.cornseed_num, 122 | "fww_min": user_param.fww_min, 123 | "deposit_fww": user_param.deposit_fww, 124 | "fwf_min": user_param.fwf_min, 125 | "deposit_fwf": user_param.deposit_fwf, 126 | "fwg_min": user_param.fwg_min, 127 | "deposit_fwg": user_param.deposit_fwg, 128 | "min_durability": user_param.min_durability, 129 | 130 | "buy_food": user_param.buy_food, 131 | "buy_food_num": user_param.buy_food_num, 132 | "buy_barley_seed": user_param.buy_barley_seed, 133 | "buy_corn_seed": user_param.buy_corn_seed, 134 | "breeding": user_param.breeding, 135 | } 136 | 137 | 138 | def load_user_param(user: dict): 139 | user_param.rpc_domain_list = user.get("rpc_domain_list", ['https://api.wax.alohaeos.com']) 140 | user_param.rpc_domain = user.get("rpc_domain", 'https://api.wax.alohaeos.com') 141 | user_param.assets_domain_list = user.get("assets_domain_list", ['https://wax.api.atomicassets.io']) 142 | user_param.assets_domain = user.get("assets_domain", 'https://wax.api.atomicassets.io') 143 | 144 | user_param.wax_account = user["wax_account"] 145 | user_param.use_proxy = user.get("use_proxy", True) 146 | user_param.proxy = user.get("proxy", None) 147 | user_param.build = user.get("build", True) 148 | user_param.mining = user.get("mining", True) 149 | user_param.chicken = user.get("chicken", True) 150 | user_param.cow = user.get("cow", True) 151 | user_param.plant = user.get("plant", True) 152 | user_param.mbs = user.get("mbs", True) 153 | user_param.mbs_mint = user.get("mbs_mint", False) 154 | user_param.sell_corn = user.get("sell_corn", False) 155 | user_param.sell_barley = user.get("sell_barley", False) 156 | user_param.sell_milk = user.get("sell_milk", False) 157 | user_param.sell_egg = user.get("sell_egg", False) 158 | user_param.auto_plant = user.get("auto_plant", False) 159 | user_param.recover_energy = user.get("recover_energy", 500) 160 | user_param.min_energy = user.get("min_energy", 50) 161 | user_param.min_durability = user.get("min_durability", 0) 162 | user_param.withdraw = user.get("withdraw", False) 163 | user_param.auto_deposit = user.get("auto_deposit", False) 164 | user_param.need_fww = user.get("need_fww", 200) 165 | user_param.need_fwf = user.get("need_fwf", 200) 166 | user_param.need_fwg = user.get("need_fwg", 200) 167 | user_param.withdraw_min = user.get("withdraw_min", 200) 168 | user_param.remaining_corn_num = user.get("remaining_corn_num", 0) 169 | user_param.remaining_barley_num = user.get("remaining_barley_num", 0) 170 | user_param.remaining_milk_num = user.get("remaining_milk_num", 0) 171 | user_param.remaining_egg_num = user.get("remaining_egg_num", 0) 172 | 173 | user_param.barleyseed_num = user.get("barleyseed_num", 0) 174 | user_param.cornseed_num = user.get("cornseed_num", 0) 175 | 176 | user_param.fww_min = user.get("fww_min", 0) 177 | user_param.deposit_fww = user.get("deposit_fww", 0) 178 | user_param.fwf_min = user.get("fwf_min", 0) 179 | user_param.deposit_fwf = user.get("deposit_fwf", 0) 180 | user_param.fwg_min = user.get("fwg_min", 0) 181 | user_param.deposit_fwg = user.get("deposit_fwg", 0) 182 | 183 | user_param.buy_food = user.get("buy_food", False) 184 | user_param.buy_food_num = user.get("buy_food_num", 0) 185 | user_param.buy_barley_seed = user.get("buy_barley_seed", False) 186 | user_param.buy_corn_seed = user.get("buy_corn_seed", False) 187 | user_param.breeding = user.get("breeding", False) 188 | 189 | 190 | cfg = Settings( 191 | path_logs="./logs/", 192 | chrome_data_dir="./data_dir/", 193 | ) 194 | -------------------------------------------------------------------------------- /user.yml.example: -------------------------------------------------------------------------------- 1 | # wax节点链接,默认使用第一个节点 2 | rpc_domain_list: 3 | - https://api.wax.alohaeos.com 4 | - https://wax.dapplica.io 5 | - https://api.waxsweden.org 6 | - https://wax.pink.gg 7 | - https://wax.eosphere.io 8 | - https://api.wax.greeneosio.com 9 | - https://wax.cryptolions.io 10 | 11 | # 原子市场节点链接,默认使用第一个节点 12 | assets_domain_list: 13 | - https://wax.api.atomicassets.io 14 | - https://atomic.wax.eosrio.io 15 | 16 | # 选中的WAX节点 17 | rpc_domain: https://api.wax.alohaeos.com 18 | # 选中的原子市场节点 19 | assets_domain: https://wax.api.atomicassets.io 20 | 21 | # wax账号 22 | wax_account: abcde.wam 23 | # only http proxy like 127.0.0.1:10809 24 | use_proxy: false 25 | proxy: null 26 | # 建造、采集资源、养鸡、养牛、种地、会员点击,需要程序自动化的操作,设置为true 27 | # 建造 28 | build: false 29 | # 采集资源 30 | mining: false 31 | # 养鸡 32 | chicken: false 33 | # 养牛 34 | cow: false 35 | # 种地浇水 36 | plant: false 37 | # 会员点击 38 | mbs: false 39 | # 繁殖喂养 40 | breeding: false 41 | # 开启会员卡存储挖矿 42 | mbs_mint: true 43 | 44 | #自动卖资产对应游戏里的Exchange 45 | #自动卖玉米 46 | sell_corn: false 47 | #保留玉米数量 48 | remaining_corn_num: 0 49 | #自动卖大麦 50 | sell_barley: false 51 | #保留大麦数量 52 | remaining_barley_num: 0 53 | #自动卖牛奶 54 | sell_milk: false 55 | #保留牛奶数量 56 | remaining_milk_num: 0 57 | #自动卖鸡蛋 58 | sell_egg: false 59 | #保留鸡蛋数量 60 | remaining_egg_num: 0 61 | 62 | # Market市场自动购买 63 | # 自动买食物[玉米或大麦](喂动物食物不够时,触发购买) 64 | buy_food: false 65 | # 一次购买的数量 66 | buy_food_num: 0 67 | # 自动买大麦种子(种地种子不够时,触发购买,数量缺多少买多少) 68 | buy_barley_seed: false 69 | # 自动买玉米种子(种地种子不够时,触发购买,数量缺多少买多少) 70 | buy_corn_seed: false 71 | 72 | 73 | # 自动种作物(大麦种子|玉米种子) 74 | # 请先买好大麦种子、玉米种子或开启市场自动购买 75 | auto_plant: false 76 | #大麦种子数量 77 | barleyseed_num: 0 78 | #玉米种子数量 79 | cornseed_num: 0 80 | 81 | # 自动提现 82 | withdraw: false 83 | # 账号中剩余多少材料不提现 84 | need_fww: 0 85 | need_fwf: 1000 86 | need_fwg: 1000 87 | #最少提现数量,3种材料总和 88 | withdraw_min: 500 89 | 90 | # 自动充值 91 | auto_deposit: false 92 | #少于多少木头充值 93 | fww_min: 0 94 | # 每次充值木头数量 95 | deposit_fww: 0 96 | # 少于多少食物充值 97 | fwf_min: 0 98 | # 每次充值食物数量 99 | deposit_fwf: 10 100 | # 少于多少金充值 101 | fwg_min: 0 102 | # 每次充值金币数量 103 | deposit_fwg: 10 104 | 105 | # 每次补充满多少能量(一般填写账号的最大能量值) 106 | recover_energy: 500 107 | # 低于多少能量开启补充(每次操作前才会检测能量够不够,如果不够开启补充) 108 | min_energy: 50 109 | 110 | #低于多少耐久度开启维修(单位%)(每次操作前才会检测能量够不够,如果不够开启维修) 111 | min_durability: 20 112 | 113 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | from datetime import datetime 3 | import platform 4 | from typing import List 5 | import shutil 6 | import os 7 | 8 | def show_time(t): 9 | if isinstance(t, datetime): 10 | return t.strftime('%Y-%m-%d %H:%M:%S') 11 | else: 12 | return datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S') 13 | 14 | 15 | class plat: 16 | name: str = None 17 | chromedriver: str = None 18 | python: str = None 19 | python_path: str = None 20 | driver_path:str =None 21 | 22 | 23 | if platform.system().lower() == "windows": 24 | plat.name = "windows" 25 | plat.chromedriver = "chromedriver.exe" 26 | plat.python = "python.exe" 27 | elif platform.system().lower() == "linux": 28 | plat.name = "linux" 29 | plat.chromedriver = "chromedriver" 30 | plat.python = "python3" 31 | elif platform.system().lower() == "darwin": 32 | plat.name = "macos" 33 | plat.chromedriver = "chromedriver" 34 | plat.python = "python3" 35 | plat.python_path = shutil.which(plat.python) 36 | plat.driver_path = shutil.which(plat.chromedriver) 37 | if not plat.driver_path: 38 | plat.driver_path = os.path.join(os.path.split(os.path.realpath(__file__))[0], plat.chromedriver) 39 | 40 | def kill_process_tree_by_id(pid: int): 41 | try: 42 | parent = psutil.Process(pid) 43 | process: List[psutil.Process] = parent.children(recursive=True) 44 | process.append(parent) 45 | for item in process: 46 | try: 47 | item.kill() 48 | except psutil.NoSuchProcess: 49 | pass 50 | except psutil.NoSuchProcess: 51 | pass 52 | 53 | 54 | def kill_process_tree_by_name(name: str): 55 | for proc in psutil.process_iter(): 56 | if proc.name() == name: 57 | kill_process_tree_by_id(proc.pid) 58 | 59 | 60 | def all_webdriver() -> List[psutil.Process]: 61 | process = [] 62 | for item in psutil.process_iter(): 63 | if item.name() == plat.chromedriver: 64 | process.append(item) 65 | return process 66 | 67 | 68 | def clear_all_webdriver(): 69 | process = all_webdriver() 70 | for item in process: 71 | kill_process_tree_by_id(item.pid) 72 | 73 | 74 | def clear_all_farmer(): 75 | for item in psutil.process_iter(): 76 | if item.name() == plat.python and "main.py" in item.cmdline(): 77 | kill_process_tree_by_id(item.pid) 78 | clear_all_webdriver() 79 | 80 | 81 | def clear_orphan_webdriver(): 82 | process = all_webdriver() 83 | killed = [] 84 | for item in process: 85 | if not item.parent(): 86 | kill_process_tree_by_id(item.pid) 87 | killed.append(item) 88 | elif item.parent().name().lower() == "systemd": 89 | kill_process_tree_by_id(item.pid) 90 | killed.append(item) 91 | return killed 92 | 93 | 94 | def test(): 95 | proc_list = [] 96 | for proc in psutil.process_iter(): 97 | if "python.exe" in proc.name(): 98 | proc_list.append(proc) 99 | print(proc.name()) 100 | print(proc.cmdline()) 101 | print(proc.exe()) 102 | 103 | 104 | if __name__ == '__main__': 105 | test() 106 | print(plat.python_path) 107 | --------------------------------------------------------------------------------