added BTMiner docstrings
This commit is contained in:
231
API/btminer.py
231
API/btminer.py
@@ -52,7 +52,9 @@ def parse_btminer_priviledge_data(token_data, data):
|
|||||||
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
||||||
decryptor = aes.decryptor()
|
decryptor = aes.decryptor()
|
||||||
# decode the message with the decryptor
|
# decode the message with the decryptor
|
||||||
ret_msg = json.loads(decryptor.update(base64.decodebytes(bytes(enc_data, encoding='utf8'))).rstrip(b'\0').decode("utf8"))
|
ret_msg = json.loads(decryptor.update(
|
||||||
|
base64.decodebytes(bytes(enc_data, encoding='utf8'))
|
||||||
|
).rstrip(b'\0').decode("utf8"))
|
||||||
return ret_msg
|
return ret_msg
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +87,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
|
|
||||||
async def send_command(self, command: str | bytes, **kwargs) -> dict:
|
async def send_command(self, command: str | bytes, **kwargs) -> dict:
|
||||||
"""Send an API command to the miner and return the result."""
|
"""Send an API command to the miner and return the result."""
|
||||||
|
# check if command is a string, if its bytes its encoded and needs to be send raw
|
||||||
if isinstance(command, str):
|
if isinstance(command, str):
|
||||||
|
# if it is a string, put it into the standard command format
|
||||||
command = json.dumps({"command": command}).encode("utf-8")
|
command = json.dumps({"command": command}).encode("utf-8")
|
||||||
try:
|
try:
|
||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
@@ -119,18 +123,27 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
|
|
||||||
|
# check if th returned data is encoded
|
||||||
if 'enc' in data.keys():
|
if 'enc' in data.keys():
|
||||||
|
# try to parse the encoded data
|
||||||
try:
|
try:
|
||||||
data = parse_btminer_priviledge_data(self.current_token, data)
|
data = parse_btminer_priviledge_data(self.current_token, data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
# if it fails to validate, it is likely an error
|
||||||
if not self.validate_command_output(data):
|
if not self.validate_command_output(data):
|
||||||
raise APIError(data["Msg"])
|
raise APIError(data["Msg"])
|
||||||
|
|
||||||
|
# return the parsed json as a dict
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def get_token(self):
|
async def get_token(self):
|
||||||
|
"""
|
||||||
|
API 'get_token' command.
|
||||||
|
|
||||||
|
Returns an encoded token and md5 password, which are used for the privileged API.
|
||||||
|
"""
|
||||||
data = await self.send_command("get_token")
|
data = await self.send_command("get_token")
|
||||||
pwd = _crypt(self.admin_pwd, "$1$" + data["Msg"]["salt"] + '$')
|
pwd = _crypt(self.admin_pwd, "$1$" + data["Msg"]["salt"] + '$')
|
||||||
pwd = pwd.split('$')
|
pwd = pwd.split('$')
|
||||||
@@ -141,7 +154,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
self.current_token = {'host_sign': host_sign, 'host_passwd_md5': host_passwd_md5}
|
self.current_token = {'host_sign': host_sign, 'host_passwd_md5': host_passwd_md5}
|
||||||
return {'host_sign': host_sign, 'host_passwd_md5': host_passwd_md5}
|
return {'host_sign': host_sign, 'host_passwd_md5': host_passwd_md5}
|
||||||
|
|
||||||
#### privileged COMMANDS ####
|
#### PRIVILEGED COMMANDS ####
|
||||||
# Please read the top of this file to learn
|
# Please read the top of this file to learn
|
||||||
# how to configure the whatsminer API to
|
# how to configure the whatsminer API to
|
||||||
# use these commands.
|
# use these commands.
|
||||||
@@ -150,8 +163,13 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
pool_1: str, worker_1: str, passwd_1: str,
|
pool_1: str, worker_1: str, passwd_1: str,
|
||||||
pool_2: str = None, worker_2: str = None, passwd_2: str = None,
|
pool_2: str = None, worker_2: str = None, passwd_2: str = None,
|
||||||
pool_3: str = None, worker_3: str = None, passwd_3: str = None):
|
pool_3: str = None, worker_3: str = None, passwd_3: str = None):
|
||||||
|
# get the token and password from the miner
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
if pool_2 and pool_3:
|
|
||||||
|
# parse pool data
|
||||||
|
if not pool_1:
|
||||||
|
raise APIError("No pools set.")
|
||||||
|
elif pool_2 and pool_3:
|
||||||
command = {
|
command = {
|
||||||
"cmd": "update_pools",
|
"cmd": "update_pools",
|
||||||
"pool1": pool_1, "worker1": worker_1, "passwd1": passwd_1,
|
"pool1": pool_1, "worker1": worker_1, "passwd1": passwd_1,
|
||||||
@@ -169,16 +187,33 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
"cmd": "update_pools",
|
"cmd": "update_pools",
|
||||||
"pool1": pool_1, "worker1": worker_1, "passwd1": passwd_1,
|
"pool1": pool_1, "worker1": worker_1, "passwd1": passwd_1,
|
||||||
}
|
}
|
||||||
|
# encode the command with the token data
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
|
# send the command
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def restart_btminer(self):
|
async def restart(self):
|
||||||
|
"""
|
||||||
|
API 'restart_btminer' command
|
||||||
|
|
||||||
|
Returns a reply informing of the restart and restarts BTMiner.
|
||||||
|
"""
|
||||||
command = {"cmd": "restart_btminer"}
|
command = {"cmd": "restart_btminer"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def power_off(self, respbefore: bool = True):
|
async def power_off(self, respbefore: bool = True):
|
||||||
|
"""
|
||||||
|
API 'power_off' command.
|
||||||
|
|
||||||
|
Powers off the mining of the miner.
|
||||||
|
|
||||||
|
Returns info on the power off.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
respbefore (optional): respond before powering off.
|
||||||
|
"""
|
||||||
if respbefore:
|
if respbefore:
|
||||||
command = {"cmd": "power_off", "respbefore": "true"}
|
command = {"cmd": "power_off", "respbefore": "true"}
|
||||||
else:
|
else:
|
||||||
@@ -188,24 +223,58 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def power_on(self):
|
async def power_on(self):
|
||||||
|
"""
|
||||||
|
API 'power_on' command.
|
||||||
|
|
||||||
|
Powers on the mining of the miner.
|
||||||
|
|
||||||
|
Returns info on the power on.
|
||||||
|
"""
|
||||||
command = {"cmd": "power_on"}
|
command = {"cmd": "power_on"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def reset_led(self):
|
async def reset_led(self):
|
||||||
|
"""
|
||||||
|
API 'reset_led' command.
|
||||||
|
|
||||||
|
Resets the LED flashing to normal.
|
||||||
|
|
||||||
|
Returns a confirmation of resetting the LED.
|
||||||
|
"""
|
||||||
command = {"cmd": "set_led", "param": "auto"}
|
command = {"cmd": "set_led", "param": "auto"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def set_led(self, color: str = "red", period: int = 2000, duration: int = 1000, start: int = 0):
|
async def set_led(self, color: str = "red", period: int = 2000, duration: int = 1000, start: int = 0):
|
||||||
|
"""
|
||||||
|
API 'set_led' command.
|
||||||
|
|
||||||
|
Sets the LED to do some pattern set with parameters.
|
||||||
|
|
||||||
|
Returns a confirmation of setting the LED.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
color: 'red' or 'green'
|
||||||
|
period: flash cycle in ms
|
||||||
|
duration: led on time in the cycle in ms
|
||||||
|
start: led on time offset in the cycle in ms
|
||||||
|
"""
|
||||||
command = {"cmd": "set_led", "color": color, "period": period, "duration": duration, "start": start}
|
command = {"cmd": "set_led", "color": color, "period": period, "duration": duration, "start": start}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def set_low_power(self):
|
async def set_low_power(self):
|
||||||
|
"""
|
||||||
|
API 'set_low_power' command.
|
||||||
|
|
||||||
|
Sets the miner to low power mode.
|
||||||
|
|
||||||
|
Returns the status of setting the miner to low power mode.
|
||||||
|
"""
|
||||||
command = {"cmd": "set_low_power"}
|
command = {"cmd": "set_low_power"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
@@ -217,18 +286,43 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
async def reboot(self):
|
async def reboot(self):
|
||||||
|
"""
|
||||||
|
API 'reboot' command.
|
||||||
|
|
||||||
|
Reboots the miner.
|
||||||
|
|
||||||
|
Returns the status of the command then reboots.
|
||||||
|
"""
|
||||||
command = {"cmd": "reboot"}
|
command = {"cmd": "reboot"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def factory_reset(self):
|
async def factory_reset(self):
|
||||||
|
"""
|
||||||
|
API 'factory_reset' command.
|
||||||
|
|
||||||
|
Resets the miner to factory defaults.
|
||||||
|
|
||||||
|
Returns the status of the command then resets.
|
||||||
|
"""
|
||||||
command = {"cmd": "factory_reset"}
|
command = {"cmd": "factory_reset"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def update_pwd(self, old_pwd: str, new_pwd: str):
|
async def update_pwd(self, old_pwd: str, new_pwd: str):
|
||||||
|
"""
|
||||||
|
API 'update_pwd' command.
|
||||||
|
|
||||||
|
Updates the admin user's password.
|
||||||
|
|
||||||
|
Returns the status of setting the password to the new password.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
old_pwd: the old admin password.
|
||||||
|
new_pwd: the new password to set. Max length of 8 bytes, using letters, numbers, and underscores.
|
||||||
|
"""
|
||||||
# check if password length is greater than 8 bytes
|
# check if password length is greater than 8 bytes
|
||||||
if len(new_pwd.encode('utf-8')) > 8:
|
if len(new_pwd.encode('utf-8')) > 8:
|
||||||
return APIError(
|
return APIError(
|
||||||
@@ -239,6 +333,13 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def set_target_freq(self, percent: int):
|
async def set_target_freq(self, percent: int):
|
||||||
|
"""
|
||||||
|
API 'set_target_freq' command.
|
||||||
|
|
||||||
|
Sets the frequency for the miner ot use.
|
||||||
|
|
||||||
|
Returns the status of setting the frequency.
|
||||||
|
"""
|
||||||
if not -10 < percent < 100:
|
if not -10 < percent < 100:
|
||||||
return APIError(f"Frequency % is outside of the allowed range. Please set a % between -10 and 100")
|
return APIError(f"Frequency % is outside of the allowed range. Please set a % between -10 and 100")
|
||||||
command = {"cmd": "set_target_freq", "percent": str(percent)}
|
command = {"cmd": "set_target_freq", "percent": str(percent)}
|
||||||
@@ -247,36 +348,82 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def enable_fast_boot(self):
|
async def enable_fast_boot(self):
|
||||||
|
"""
|
||||||
|
API 'enable_fast_boot' command.
|
||||||
|
|
||||||
|
Turns on the fast boot feature on the miner.
|
||||||
|
|
||||||
|
Returns the status of setting the fast boot to on.
|
||||||
|
"""
|
||||||
command = {"cmd": "enable_btminer_fast_boot"}
|
command = {"cmd": "enable_btminer_fast_boot"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def disable_fast_boot(self):
|
async def disable_fast_boot(self):
|
||||||
|
"""
|
||||||
|
API 'disable'_fast_boot' command.
|
||||||
|
|
||||||
|
Turns off the fast boot feature on the miner.
|
||||||
|
|
||||||
|
Returns the status of setting the fast boot to off.
|
||||||
|
"""
|
||||||
command = {"cmd": "disable_btminer_fast_boot"}
|
command = {"cmd": "disable_btminer_fast_boot"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def enable_web_pools(self):
|
async def enable_web_pools(self):
|
||||||
|
"""
|
||||||
|
API 'enable_web_pools' command.
|
||||||
|
|
||||||
|
Turns on the ability to change the pools through the web interface.
|
||||||
|
|
||||||
|
Returns the status of setting the web pools to enabled.
|
||||||
|
"""
|
||||||
command = {"cmd": "enable_web_pools"}
|
command = {"cmd": "enable_web_pools"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def disable_web_pools(self):
|
async def disable_web_pools(self):
|
||||||
|
"""
|
||||||
|
API 'disable_web_pools' command.
|
||||||
|
|
||||||
|
Turns off the ability to change the pools through the web interface.
|
||||||
|
|
||||||
|
Returns the status of setting the web pools to disabled.
|
||||||
|
"""
|
||||||
command = {"cmd": "disable_web_pools"}
|
command = {"cmd": "disable_web_pools"}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def set_hostname(self, hostname: str):
|
async def set_hostname(self, hostname: str):
|
||||||
|
"""
|
||||||
|
API 'set_hostname' command.
|
||||||
|
|
||||||
|
Sets the hostname of the miner.
|
||||||
|
|
||||||
|
Returns the status of setting the hostname.
|
||||||
|
"""
|
||||||
command = {"cmd": "set_hostname", "hostname": hostname}
|
command = {"cmd": "set_hostname", "hostname": hostname}
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def set_power_pct(self, percent: int):
|
async def set_power_pct(self, percent: int):
|
||||||
|
"""
|
||||||
|
API 'set_power_pct' command.
|
||||||
|
|
||||||
|
Sets the percent of power the miner should use.
|
||||||
|
|
||||||
|
Returns the status of setting the power usage to this percent.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
percent: the percent to set the power usage to, between 0 and 100.
|
||||||
|
"""
|
||||||
|
|
||||||
if not 0 < percent < 100:
|
if not 0 < percent < 100:
|
||||||
return APIError(f"Power PCT % is outside of the allowed range. Please set a % between 0 and 100")
|
return APIError(f"Power PCT % is outside of the allowed range. Please set a % between 0 and 100")
|
||||||
command = {"cmd": "set_power_pct", "percent": str(percent)}
|
command = {"cmd": "set_power_pct", "percent": str(percent)}
|
||||||
@@ -285,6 +432,18 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
return await self.send_command(enc_command)
|
return await self.send_command(enc_command)
|
||||||
|
|
||||||
async def pre_power_on(self, complete: bool, msg: str):
|
async def pre_power_on(self, complete: bool, msg: str):
|
||||||
|
"""
|
||||||
|
API 'pre_power_on' command.
|
||||||
|
|
||||||
|
Preheats the miner for the 'power_on' command. Can also be used to query the status of pre powering on.
|
||||||
|
|
||||||
|
Returns status of pre powering on.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
complete: check whether or not it is complete.
|
||||||
|
msg: the message to check. "wait for adjust temp" or "adjust complete" or "adjust continue"
|
||||||
|
"""
|
||||||
|
|
||||||
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
|
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
|
||||||
return APIError(
|
return APIError(
|
||||||
'Message is incorrect, please choose one of '
|
'Message is incorrect, please choose one of '
|
||||||
@@ -302,28 +461,88 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
#### END privileged COMMANDS ####
|
#### END privileged COMMANDS ####
|
||||||
|
|
||||||
async def summary(self):
|
async def summary(self):
|
||||||
|
"""
|
||||||
|
API 'summary' command.
|
||||||
|
|
||||||
|
Returns a dict containing the status summary of the miner.
|
||||||
|
"""
|
||||||
return await self.send_command("summary")
|
return await self.send_command("summary")
|
||||||
|
|
||||||
async def pools(self):
|
async def pools(self):
|
||||||
|
"""
|
||||||
|
API 'pools' command.
|
||||||
|
|
||||||
|
Returns a dict containing the status of each pool.
|
||||||
|
"""
|
||||||
return await self.send_command("pools")
|
return await self.send_command("pools")
|
||||||
|
|
||||||
async def devs(self):
|
async def devs(self):
|
||||||
|
"""
|
||||||
|
API 'devs' command.
|
||||||
|
|
||||||
|
Returns a dict containing each PGA/ASC with their details.
|
||||||
|
"""
|
||||||
return await self.send_command("devs")
|
return await self.send_command("devs")
|
||||||
|
|
||||||
async def edevs(self):
|
async def edevs(self):
|
||||||
|
"""
|
||||||
|
API 'edevs' command.
|
||||||
|
|
||||||
|
Returns a dict containing each PGA/ASC with their details,
|
||||||
|
ignoring blacklisted devices and zombie devices.
|
||||||
|
"""
|
||||||
return await self.send_command("edevs")
|
return await self.send_command("edevs")
|
||||||
|
|
||||||
async def devdetails(self):
|
async def devdetails(self):
|
||||||
|
"""
|
||||||
|
API 'devdetails' command.
|
||||||
|
|
||||||
|
Returns a dict containing all devices with their static details.
|
||||||
|
"""
|
||||||
return await self.send_command("devdetails")
|
return await self.send_command("devdetails")
|
||||||
|
|
||||||
async def get_psu(self):
|
async def get_psu(self):
|
||||||
|
"""
|
||||||
|
API 'get_psu' command.
|
||||||
|
|
||||||
|
Returns a dict containing PSU and power information.
|
||||||
|
"""
|
||||||
return await self.send_command("get_psu")
|
return await self.send_command("get_psu")
|
||||||
|
|
||||||
async def version(self):
|
async def version(self):
|
||||||
|
"""
|
||||||
|
API 'get_version' command.
|
||||||
|
|
||||||
|
Returns a dict containing version information.
|
||||||
|
"""
|
||||||
return await self.send_command("get_version")
|
return await self.send_command("get_version")
|
||||||
|
|
||||||
async def status(self):
|
async def status(self):
|
||||||
|
"""
|
||||||
|
API 'status' command.
|
||||||
|
|
||||||
|
Returns a dict containing BTMiner status and firmware version.
|
||||||
|
"""
|
||||||
return await self.send_command("status")
|
return await self.send_command("status")
|
||||||
|
|
||||||
async def get_miner_info(self):
|
async def get_miner_info(self, info: str | list):
|
||||||
return await self.send_command("get_miner_info")
|
"""
|
||||||
|
API 'get_miner_info' command.
|
||||||
|
|
||||||
|
Returns a dict containing requested information.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
info: the info that you want to get.
|
||||||
|
"ip",
|
||||||
|
"proto",
|
||||||
|
"netmask",
|
||||||
|
"gateway",
|
||||||
|
"dns",
|
||||||
|
"hostname",
|
||||||
|
"mac",
|
||||||
|
"ledstat".
|
||||||
|
"""
|
||||||
|
if isinstance(info, str):
|
||||||
|
return await self.send_command("get_miner_info", parameters=info)
|
||||||
|
else:
|
||||||
|
return await self.send_command("get_miner_info", parameters=f"{','.join([str(item) for item in info])}")
|
||||||
|
|||||||
Reference in New Issue
Block a user