mirror of
https://github.com/NohamR/TransmissionBot.git
synced 2025-05-24 14:22:00 +00:00
added features
Repeat option for `summary` and `list` commands More filters: stalled, running (non-zero rate), error (and types of error), verifying data, queued, public/private (tracker) Added option to verify data to `modify` command Added more info to summary.
This commit is contained in:
parent
a4dfb21ebe
commit
4d0e3d6854
547
bot.py
547
bot.py
@ -18,11 +18,14 @@ import os
|
|||||||
from os.path import expanduser, join
|
from os.path import expanduser, join
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
|
import time
|
||||||
import pytz
|
import pytz
|
||||||
import platform
|
import platform
|
||||||
import transmissionrpc
|
import transmissionrpc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
# BEGIN USER CONFIGURATION
|
||||||
|
|
||||||
BOT_PREFIX = 't/'
|
BOT_PREFIX = 't/'
|
||||||
TOKEN = 'SECRET_TOKEN'
|
TOKEN = 'SECRET_TOKEN'
|
||||||
OWNERS = [OWNER_USER_IDS]
|
OWNERS = [OWNER_USER_IDS]
|
||||||
@ -31,31 +34,74 @@ WHITELIST = [USER_IDS]
|
|||||||
CHANNEL_IDS=[CHANNEL_IDS]
|
CHANNEL_IDS=[CHANNEL_IDS]
|
||||||
LOGO_URL="https://iyanovich.files.wordpress.com/2009/04/transmission-logo.png"
|
LOGO_URL="https://iyanovich.files.wordpress.com/2009/04/transmission-logo.png"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TSCLIENT_CONFIG={
|
TSCLIENT_CONFIG={
|
||||||
'host': "10.0.1.2",
|
'host': "10.0.1.2",
|
||||||
'port': 9091,
|
'port': 9091,
|
||||||
'user': "USERNAME",
|
'user': "USERNAME",
|
||||||
'password': "PASSWORD"
|
'password': "PASSWORD"
|
||||||
}
|
}
|
||||||
|
|
||||||
DRYRUN = False
|
DRYRUN = False
|
||||||
|
REPEAT_FREQ = 5 # time in seconds to wait between reprinting repeated commands (in addition to the time requred to delete old message(s) and add reactions)
|
||||||
|
REPEAT_TIMEOUT = 45 # time in seconds before a repeated command automatically stops
|
||||||
|
|
||||||
logging.basicConfig(format='%(asctime)s %(message)s',filename=join(expanduser("~"),'transmissionbot.log'))
|
logging.basicConfig(format='%(asctime)s %(message)s',filename=join(expanduser("~"),'transmissionbot.log'))
|
||||||
|
|
||||||
|
# END USER CONFIGURATION
|
||||||
|
|
||||||
|
REPEAT_COMMAND = False
|
||||||
|
REPEAT_MSG_LIST = []
|
||||||
|
REPEAT_START_TIME = 0
|
||||||
|
|
||||||
client = Bot(command_prefix=BOT_PREFIX)
|
client = Bot(command_prefix=BOT_PREFIX)
|
||||||
TSCLIENT = None
|
TSCLIENT = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('transmission_bot')
|
logger = logging.getLogger('transmission_bot')
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
DEFAULT_REASON="TransmissionBot"
|
DEFAULT_REASON="TransmissionBot"
|
||||||
|
|
||||||
# Begin transmissionrpc functions, lovingly taken from https://github.com/leighmacdonald/transmission_scripts
|
# Begin transmissionrpc functions, lovingly taken from https://github.com/leighmacdonald/transmission_scripts
|
||||||
|
|
||||||
|
filter_names = ( # these are the filters accepted by transmissionrpc
|
||||||
|
"all",
|
||||||
|
"active",
|
||||||
|
"downloading",
|
||||||
|
"seeding",
|
||||||
|
"stopped",
|
||||||
|
"finished"
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_names_extra = ( # these are extra filters I've added
|
||||||
|
"stalled",
|
||||||
|
"private",
|
||||||
|
"public",
|
||||||
|
"error",
|
||||||
|
'err_none',
|
||||||
|
'err_tracker_warn',
|
||||||
|
'err_tracker_error',
|
||||||
|
'err_local',
|
||||||
|
'verifying',
|
||||||
|
'queued',
|
||||||
|
"running" # running means a non-zero transfer rate, not to be confused with "active"
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_names_full = filter_names + filter_names_extra
|
||||||
|
|
||||||
|
sort_names = (
|
||||||
|
"id",
|
||||||
|
"progress",
|
||||||
|
"name",
|
||||||
|
"size",
|
||||||
|
"ratio",
|
||||||
|
"speed",
|
||||||
|
"speed_up",
|
||||||
|
"speed_down",
|
||||||
|
"status",
|
||||||
|
"queue",
|
||||||
|
"age",
|
||||||
|
"activity"
|
||||||
|
)
|
||||||
|
|
||||||
class TSClient(transmissionrpc.Client):
|
class TSClient(transmissionrpc.Client):
|
||||||
""" Basic subclass of the standard transmissionrpc client which provides some simple
|
""" Basic subclass of the standard transmissionrpc client which provides some simple
|
||||||
helper functionality.
|
helper functionality.
|
||||||
@ -80,13 +126,29 @@ class TSClient(transmissionrpc.Client):
|
|||||||
if id_list:
|
if id_list:
|
||||||
torrents = [tor for tor in torrents if tor.id in id_list]
|
torrents = [tor for tor in torrents if tor.id in id_list]
|
||||||
if filter_by:
|
if filter_by:
|
||||||
torrents = filter_torrents_by(torrents, key=getattr(Filter, filter_by))
|
for f in filter_by.split():
|
||||||
|
if f in filter_names:
|
||||||
|
torrents = filter_torrents_by(torrents, key=getattr(Filter, filter_by))
|
||||||
|
elif f == "verifying":
|
||||||
|
torrents = [t for t in torrents if "check" in t.status]
|
||||||
|
elif f == "queued":
|
||||||
|
torrents = [t for t in torrents if "load pending" in t.status]
|
||||||
|
elif f == "stalled":
|
||||||
|
torrents = [t for t in torrents if t.isStalled]
|
||||||
|
elif f == "private":
|
||||||
|
torrents = [t for t in torrents if t.isPrivate]
|
||||||
|
elif f == "public":
|
||||||
|
torrents = [t for t in torrents if not t.isPrivate]
|
||||||
|
elif f == "error":
|
||||||
|
torrents = [t for t in torrents if t.error != 0]
|
||||||
|
elif f == "running":
|
||||||
|
torrents = [t for t in torrents if t.rateDownload + t.rateUpload > 0]
|
||||||
|
else:
|
||||||
|
continue
|
||||||
if sort_by is None:
|
if sort_by is None:
|
||||||
if filter_by == "downloading":
|
if "downloading" in filter_by or "seeding" in filter_by or "running" in filter_by:
|
||||||
sort_by = "speed_down"
|
sort_by = "speed"
|
||||||
elif filter_by == "seeding":
|
elif "stopped" in filter_by or "finished" in filter_by:
|
||||||
sort_by = "speed_up"
|
|
||||||
elif filter_by in ["stopped","finished"]:
|
|
||||||
sort_by = "ratio"
|
sort_by = "ratio"
|
||||||
if sort_by:
|
if sort_by:
|
||||||
torrents = sort_torrents_by(torrents, key=getattr(Sort, sort_by), reverse=reverse)
|
torrents = sort_torrents_by(torrents, key=getattr(Sort, sort_by), reverse=reverse)
|
||||||
@ -111,26 +173,18 @@ def make_client():
|
|||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
filter_names = (
|
|
||||||
"all",
|
|
||||||
"active",
|
|
||||||
"downloading",
|
|
||||||
"seeding",
|
|
||||||
"stopped",
|
|
||||||
"finished"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Filter(object):
|
class Filter(object):
|
||||||
"""A set of filtering operations that can be used against a list of torrent objects"""
|
"""A set of filtering operations that can be used against a list of torrent objects"""
|
||||||
|
|
||||||
names = (
|
# names = (
|
||||||
"all",
|
# "all",
|
||||||
"active",
|
# "active",
|
||||||
"downloading",
|
# "downloading",
|
||||||
"seeding",
|
# "seeding",
|
||||||
"stopped",
|
# "stopped",
|
||||||
"finished"
|
# "finished"
|
||||||
)
|
# )
|
||||||
|
names = filter_names
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def all(t):
|
def all(t):
|
||||||
@ -174,38 +228,24 @@ def filter_torrents_by(torrents, key=Filter.all):
|
|||||||
filtered_torrents.append(torrent)
|
filtered_torrents.append(torrent)
|
||||||
return filtered_torrents
|
return filtered_torrents
|
||||||
|
|
||||||
sort_names = (
|
|
||||||
"id",
|
|
||||||
"progress",
|
|
||||||
"name",
|
|
||||||
"size",
|
|
||||||
"ratio",
|
|
||||||
"speed",
|
|
||||||
"speed_up",
|
|
||||||
"speed_down",
|
|
||||||
"status",
|
|
||||||
"queue",
|
|
||||||
"age",
|
|
||||||
"activity"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Sort(object):
|
class Sort(object):
|
||||||
""" Defines methods for sorting torrent sequences """
|
""" Defines methods for sorting torrent sequences """
|
||||||
|
|
||||||
names = (
|
# names = (
|
||||||
"id",
|
# "id",
|
||||||
"progress",
|
# "progress",
|
||||||
"name",
|
# "name",
|
||||||
"size",
|
# "size",
|
||||||
"ratio",
|
# "ratio",
|
||||||
"speed",
|
# "speed",
|
||||||
"speed_up",
|
# "speed_up",
|
||||||
"speed_down",
|
# "speed_down",
|
||||||
"status",
|
# "status",
|
||||||
"queue",
|
# "queue",
|
||||||
"age",
|
# "age",
|
||||||
"activity"
|
# "activity"
|
||||||
)
|
# )
|
||||||
|
names = sort_names
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def activity(t):
|
def activity(t):
|
||||||
@ -350,6 +390,23 @@ def resume_torrents(torrents=[], reason=DEFAULT_REASON):
|
|||||||
TSCLIENT.start_torrent(torrent.hashString)
|
TSCLIENT.start_torrent(torrent.hashString)
|
||||||
logger.info("Resumed: {} {}\n\tReason: {}\n\tDry run: {}".format(torrent.name, torrent.hashString, reason, DRYRUN))
|
logger.info("Resumed: {} {}\n\tReason: {}\n\tDry run: {}".format(torrent.name, torrent.hashString, reason, DRYRUN))
|
||||||
|
|
||||||
|
def verify_torrents(torrents=[]):
|
||||||
|
""" Verify a list of torrents from the client.
|
||||||
|
|
||||||
|
:param client: Transmission RPC Client
|
||||||
|
:type client: transmissionrpc.Client
|
||||||
|
:param torrent: Torrent instance to remove
|
||||||
|
:type torrent: transmissionrpc.Torrent
|
||||||
|
:type reason: str
|
||||||
|
:param dry_run: Do a dry run without actually running any commands
|
||||||
|
:type dry_run: bool
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for torrent in (torrents if len(torrents) > 0 else TSCLIENT.get_torrents()):
|
||||||
|
if not DRYRUN:
|
||||||
|
TSCLIENT.verify_torrent(torrent.hashString)
|
||||||
|
logger.info("Verified: {} {}\n\tDry run: {}".format(torrent.name, torrent.hashString, DRYRUN))
|
||||||
|
|
||||||
def add_torrent(torStr):
|
def add_torrent(torStr):
|
||||||
tor = None
|
tor = None
|
||||||
if torStr != "":
|
if torStr != "":
|
||||||
@ -420,12 +477,13 @@ async def CommandPrecheck(context):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@client.command(name='add', pass_context=True)
|
@client.command(name='add', aliases=['a'], pass_context=True)
|
||||||
async def add(context, *, content):
|
async def add(context, *, content):
|
||||||
if await CommandPrecheck(context):
|
if await CommandPrecheck(context):
|
||||||
if content == "":
|
if content == "":
|
||||||
await context.message.channel.send("Invalid string")
|
await context.message.channel.send("Invalid string")
|
||||||
else:
|
else:
|
||||||
|
await context.message.delete()
|
||||||
torStr = None
|
torStr = None
|
||||||
for t in content.strip().split(" "):
|
for t in content.strip().split(" "):
|
||||||
await context.message.channel.send('Adding torrent {}\n Please wait...'.format(t))
|
await context.message.channel.send('Adding torrent {}\n Please wait...'.format(t))
|
||||||
@ -435,13 +493,10 @@ async def add(context, *, content):
|
|||||||
else:
|
else:
|
||||||
torStr = tor.name
|
torStr = tor.name
|
||||||
await context.message.channel.send('✅ Added torrent{}:\n{}'.format("s" if len(content.strip().split(" ")) > 1 else "", torStr))
|
await context.message.channel.send('✅ Added torrent{}:\n{}'.format("s" if len(content.strip().split(" ")) > 1 else "", torStr))
|
||||||
@client.command(name='a', pass_context=True)
|
|
||||||
async def a(context, *, content=""):
|
|
||||||
await add(context, content=content)
|
|
||||||
|
|
||||||
# def torInfo(t):
|
# def torInfo(t):
|
||||||
# states = ('downloading', 'seeding', 'stopped', 'finished','all')
|
# states = ('downloading', 'seeding', 'stopped', 'finished','all')
|
||||||
# stateEmoji = {i:j for i,j in zip(states,['🔻','🌱','⏸','🏁','🔁'])}
|
# stateEmoji = {i:j for i,j in zip(states,['🔻','🌱','⏸','🏁','↕️'])}
|
||||||
#
|
#
|
||||||
# downStr = humanbytes(t.progress * 0.01 * t.totalSize)
|
# downStr = humanbytes(t.progress * 0.01 * t.totalSize)
|
||||||
# upStr = "{} (Ratio: {:.2f})".format(humanbytes(t.uploadedEver), t.uploadRatio)
|
# upStr = "{} (Ratio: {:.2f})".format(humanbytes(t.uploadedEver), t.uploadRatio)
|
||||||
@ -454,21 +509,170 @@ async def a(context, *, content=""):
|
|||||||
# have = "{} ({:d}){}{}".format(humanbytes(t.totalSize), t.progress, '' if t.haveUnchecked == 0 else ', {} Unverified'.format(humanbytes(t.haveUnchecked)), '' if t.corruptEver == 0 else ', {} Corrupt'.format(humanbytes(t.corruptEver)))
|
# have = "{} ({:d}){}{}".format(humanbytes(t.totalSize), t.progress, '' if t.haveUnchecked == 0 else ', {} Unverified'.format(humanbytes(t.haveUnchecked)), '' if t.corruptEver == 0 else ', {} Corrupt'.format(humanbytes(t.corruptEver)))
|
||||||
# avail = "100%"
|
# avail = "100%"
|
||||||
#
|
#
|
||||||
#
|
|
||||||
#
|
|
||||||
# embed=discord.Embed(title=t.name,color=0xb51a00)
|
# embed=discord.Embed(title=t.name,color=0xb51a00)
|
||||||
#
|
#
|
||||||
# return embed
|
# return embed
|
||||||
|
|
||||||
|
torStates = ('downloading', 'seeding', 'stopped', 'verifying', 'queued', 'finished', #0-5
|
||||||
|
'stalled', 'active', 'running', #6-8
|
||||||
|
'private', 'public', #9-10
|
||||||
|
'error', 'err_none', 'err_tracker_warn', 'err_tracker_error', 'err_local', # 11-
|
||||||
|
)
|
||||||
|
torStateEmoji = ('🔻','🌱','⏸','🩺','🚧','🏁',
|
||||||
|
'🐢','🐇','🚀',
|
||||||
|
'🔒','🔓',
|
||||||
|
'‼️','✅','⚠️','🌐','🖥'
|
||||||
|
)
|
||||||
|
torStateFilters = {i:"--filter {}".format(j) for i,j in zip(torStateEmoji,torStates)}
|
||||||
|
torStateFilters['↕️']=''
|
||||||
|
|
||||||
def torList(torrents, author_name="Torrent Transfers",title=None,description=None):
|
def numTorInState(torrents, state):
|
||||||
states = ('downloading', 'seeding', 'stopped', 'finished','all')
|
rpc_states = ('downloading', 'seeding', 'stopped', 'finished')
|
||||||
stateEmoji = {i:j for i,j in zip(states,['🔻','🌱','⏸','🏁','🔁'])}
|
if state in rpc_states:
|
||||||
|
return len([True for t in torrents if t.status == state])
|
||||||
|
elif state =='verifying': # these are also rpc statuses, but I want to combine them.
|
||||||
|
return len([True for t in torrents if 'check' in t.status])
|
||||||
|
elif state == 'queued':
|
||||||
|
return len([True for t in torrents if 'load pending' in t.status])
|
||||||
|
elif state == 'stalled':
|
||||||
|
return len([True for t in torrents if t.isStalled])
|
||||||
|
elif state == 'active':
|
||||||
|
return len([True for t in torrents if not t.isStalled]) - len([True for t in torrents if t.rateDownload + t.rateUpload > 0])
|
||||||
|
elif state == 'running':
|
||||||
|
return len([True for t in torrents if t.rateDownload + t.rateUpload > 0])
|
||||||
|
elif state == 'private':
|
||||||
|
return len([True for t in torrents if t.isPrivate])
|
||||||
|
elif state == 'public':
|
||||||
|
return len([True for t in torrents if not t.isPrivate])
|
||||||
|
elif state == 'error':
|
||||||
|
return len([True for t in torrents if t.error != 0])
|
||||||
|
elif state == 'err_none':
|
||||||
|
return len([True for t in torrents if t.error == 0])
|
||||||
|
elif state == 'err_twarn':
|
||||||
|
return len([True for t in torrents if t.error == 1])
|
||||||
|
elif state == 'err_terr':
|
||||||
|
return len([True for t in torrents if t.error == 2])
|
||||||
|
elif state == 'err_local':
|
||||||
|
return len([True for t in torrents if t.error == 3])
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def torSummary(torrents, repeat=False):
|
||||||
|
numInState = [numTorInState(torrents,s) for s in torStates]
|
||||||
|
numTot = len(torrents)
|
||||||
|
|
||||||
|
sumTot = sum([t.totalSize for t in torrents])
|
||||||
|
totSize = humanbytes(sumTot)
|
||||||
|
totUpRate = humanbytes(sum([t.rateUpload for t in torrents]))
|
||||||
|
totDownRate = humanbytes(sum([t.rateDownload for t in torrents]))
|
||||||
|
|
||||||
|
downList = [t.progress*0.01*t.totalSize for t in torrents]
|
||||||
|
upList = [t.ratio * j for t,j in zip(torrents,downList)]
|
||||||
|
|
||||||
|
sumDown = sum(downList)
|
||||||
|
sumUp = sum(upList)
|
||||||
|
|
||||||
|
totDown = humanbytes(sumDown)
|
||||||
|
totUp = humanbytes(sumUp)
|
||||||
|
|
||||||
|
totRatio = '{:.2f}'.format(sumUp / sumDown)
|
||||||
|
|
||||||
|
totDownRatio = '{:.2f}'.format(sumDown / sumTot * 100.0)
|
||||||
|
|
||||||
|
numTopRatios = min([len(torrents),5])
|
||||||
|
topRatios = "• Top {} ratio{}:".format(numTopRatios,"s" if numTopRatios != 1 else "")
|
||||||
|
sortByRatio = sorted(torrents,key=lambda t:float(t.ratio),reverse=True)
|
||||||
|
for i in range(numTopRatios):
|
||||||
|
topRatios += "\n {:.1f} {:.35}{}".format(float(sortByRatio[i].ratio),sortByRatio[i].name,"..." if len(sortByRatio[i].name) > 35 else "")
|
||||||
|
|
||||||
|
embed=discord.Embed(description="*React to see list of corresponding transfers*", color=0xb51a00)
|
||||||
|
embed.set_author(name="Torrent Summary 🌊", icon_url=LOGO_URL)
|
||||||
|
embed.add_field(name="⬇️ {}/s".format(totDownRate), value="⬆️ {}/s".format(totUpRate), inline=False)
|
||||||
|
embed.add_field(name="⏬ {} of {}".format(totDown,totSize), value="⏫ {} ⚖️ {}".format(totUp,totRatio), inline=False)
|
||||||
|
embed.add_field(name="↕️ {} transfer{}".format(numTot, 's' if numTot != 1 else ''), value=' '.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[:6], numInState[:6])]), inline=False)
|
||||||
|
embed.add_field(name="{} Error{}".format(numInState[11], 's' if numInState[9] != 1 else ''), value='\n'.join(['{} {}'.format(i,"**{}**".format(j) if i != '✅' and j > 0 else j) for i,j in zip(torStateEmoji[12:], numInState[12:])]), inline=True)
|
||||||
|
embed.add_field(name="Activity", value='\n'.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[6:9], numInState[6:9])]), inline=True)
|
||||||
|
embed.add_field(name="Tracker", value='\n'.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[9:11], numInState[9:11])]), inline=True)
|
||||||
|
|
||||||
|
embed.set_footer(text=topRatios+"\n📜 Symbol legend{}".format('\nUpdating every {} second{}—❎ to stop'.format(REPEAT_FREQ,'s' if REPEAT_FREQ != 1 else '') if repeat else ', 🔄 to auto-update'))
|
||||||
|
# await context.message.channel.send(embed=embed)
|
||||||
|
return embed,numInState
|
||||||
|
|
||||||
|
@client.command(name='summary',aliases=['s'], pass_context=True)
|
||||||
|
async def summary(context, *, content="", repeat=False):
|
||||||
|
global REPEAT_COMMAND, REPEAT_MSG_LIST
|
||||||
|
if await CommandPrecheck(context):
|
||||||
|
if not repeat:
|
||||||
|
await context.message.delete()
|
||||||
|
stateEmoji = ('📜','❎' if repeat else '🔄','↕️') + torStateEmoji
|
||||||
|
ignoreEmoji = ('✅')
|
||||||
|
|
||||||
|
summaryData=torSummary(TSCLIENT.get_torrents(), repeat=repeat)
|
||||||
|
|
||||||
|
if repeat:
|
||||||
|
msg = REPEAT_MSG_LIST[0]
|
||||||
|
await msg.edit(embed=summaryData[0])
|
||||||
|
else:
|
||||||
|
msg = await context.message.channel.send(embed=summaryData[0])
|
||||||
|
|
||||||
|
msgRxns = [str(r.emoji) for r in msg.reactions]
|
||||||
|
|
||||||
|
for i in stateEmoji[:2]:
|
||||||
|
if i not in msgRxns:
|
||||||
|
await msg.add_reaction(i)
|
||||||
|
for i in range(len(summaryData[1])):
|
||||||
|
if summaryData[1][i] > 0 and stateEmoji[i+3] not in ignoreEmoji and stateEmoji[i+3] not in msgRxns:
|
||||||
|
await msg.add_reaction(stateEmoji[i+3])
|
||||||
|
elif summaryData[1][i] == 0 and stateEmoji[i+3] in msgRxns:
|
||||||
|
await msg.clear_reaction(stateEmoji[i+3])
|
||||||
|
|
||||||
|
def check(reaction, user):
|
||||||
|
return user == context.message.author and reaction.message.id == msg.id and str(reaction.emoji) in stateEmoji
|
||||||
|
|
||||||
|
try:
|
||||||
|
reaction, user = await client.wait_for('reaction_add', timeout=60.0 if not repeat else REPEAT_FREQ, check=check)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if str(reaction.emoji) in stateEmoji[2:] and str(reaction.emoji) not in ignoreEmoji:
|
||||||
|
await list_transfers(context, content=torStateFilters[str(reaction.emoji)])
|
||||||
|
elif str(reaction.emoji) == stateEmoji[0]:
|
||||||
|
await legend(context)
|
||||||
|
elif str(reaction.emoji) == stateEmoji[1]:
|
||||||
|
if repeat:
|
||||||
|
REPEAT_COMMAND = False
|
||||||
|
REPEAT_MSG_LIST = []
|
||||||
|
await context.message.channel.send("❎ Auto-update cancelled...")
|
||||||
|
else:
|
||||||
|
REPEAT_MSG_LIST = [msg]
|
||||||
|
await msg.clear_reaction('🔄')
|
||||||
|
await repeat_command(summary, context=context, content=content)
|
||||||
|
|
||||||
|
def strListToList(strList):
|
||||||
|
if not re.match('^[0-9\,\-]+$', strList):
|
||||||
|
return False
|
||||||
|
outList = []
|
||||||
|
for seg in strList.strip().split(","):
|
||||||
|
subseg = seg.split("-")
|
||||||
|
if len(subseg) == 1 and int(subseg[0]) not in outList:
|
||||||
|
outList.append(int(subseg[0]))
|
||||||
|
elif len(subseg) == 2:
|
||||||
|
subseg = sorted([int(i) for i in subseg])
|
||||||
|
outList += range(subseg[0],subseg[1]+1)
|
||||||
|
if len(outList) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return outList
|
||||||
|
|
||||||
|
|
||||||
|
def torList(torrents, author_name="Torrent Transfers",title=None,description=None,repeat=False):
|
||||||
|
states = ('downloading', 'seeding', 'stopped', 'finished','checking','check pending','download pending','upload pending')
|
||||||
|
stateEmoji = {i:j for i,j in zip(states,['🔻','🌱','⏸','🏁','🩺','🩺','🚧','🚧'])}
|
||||||
errorStrs = ['✅','⚠️','🌐','🖥']
|
errorStrs = ['✅','⚠️','🌐','🖥']
|
||||||
|
|
||||||
def torListLine(t):
|
def torListLine(t):
|
||||||
down = humanbytes(t.progress * 0.01 * t.totalSize)
|
down = humanbytes(t.progress * 0.01 * t.totalSize)
|
||||||
out = "{} {} {} {} ".format(stateEmoji[t.status],errorStrs[t.error],'🐢' if t.isStalled else '🐇', '🔒' if t.isPrivate else '🔓')
|
out = "{} {} {} {} ".format(stateEmoji[t.status],errorStrs[t.error],'🚀' if t.rateDownload + t.rateUpload > 0 else '🐢' if t.isStalled else '🐇', '🔒' if t.isPrivate else '🔓')
|
||||||
if t.status == 'downloading':
|
if t.status == 'downloading':
|
||||||
out += "{}/{} ⬇️ {}/s ⬆️ {}/s ⚖️ {:.2f}".format(down,humanbytes(t.totalSize),humanbytes(t.rateDownload),humanbytes(t.rateUpload),t.uploadRatio)
|
out += "{}/{} ⬇️ {}/s ⬆️ {}/s ⚖️ {:.2f}".format(down,humanbytes(t.totalSize),humanbytes(t.rateDownload),humanbytes(t.rateUpload),t.uploadRatio)
|
||||||
elif t.status == 'seeding':
|
elif t.status == 'seeding':
|
||||||
@ -478,6 +682,9 @@ def torList(torrents, author_name="Torrent Transfers",title=None,description=Non
|
|||||||
elif t.status == 'finished':
|
elif t.status == 'finished':
|
||||||
out += "{} ⚖️ {:.2f}".format(humanbytes(t.totalSize),t.uploadRatio)
|
out += "{} ⚖️ {:.2f}".format(humanbytes(t.totalSize),t.uploadRatio)
|
||||||
|
|
||||||
|
if t.error != 0:
|
||||||
|
out += "\n**Error:** *{}*".format(t.errorString)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
nameList = ["{}) {:.245}{}".format(t.id,t.name,"..." if len(t.name) > 245 else "") for t in torrents]
|
nameList = ["{}) {:.245}{}".format(t.id,t.name,"..." if len(t.name) > 245 else "") for t in torrents]
|
||||||
@ -542,133 +749,102 @@ def torGetListOpsFromStr(listOpStr):
|
|||||||
if filter_regex == "":
|
if filter_regex == "":
|
||||||
filter_regex = None
|
filter_regex = None
|
||||||
|
|
||||||
if filter_by is not None and filter_by not in filter_names:
|
if filter_by is not None and filter_by not in filter_names_full:
|
||||||
return -1, None, None
|
return -1, None, None
|
||||||
if sort_by is not None and sort_by not in sort_names:
|
if sort_by is not None and sort_by not in sort_names:
|
||||||
return None, -1, None
|
return None, -1, None
|
||||||
|
|
||||||
return filter_by, sort_by, filter_regex
|
return filter_by, sort_by, filter_regex
|
||||||
|
|
||||||
@client.command(name='list', pass_context=True)
|
async def repeat_command(command, context, content=""):
|
||||||
async def list(context, *, content=""):
|
global REPEAT_COMMAND, REPEAT_MSG_LIST
|
||||||
|
REPEAT_COMMAND = True
|
||||||
|
start_time = datetime.datetime.now()
|
||||||
|
while REPEAT_COMMAND:
|
||||||
|
delta = datetime.datetime.now() - start_time
|
||||||
|
if delta.seconds >= REPEAT_TIMEOUT:
|
||||||
|
await context.message.channel.send("❎ Auto-update timed out...")
|
||||||
|
REPEAT_COMMAND = False
|
||||||
|
REPEAT_MSG_LIST = []
|
||||||
|
return
|
||||||
|
# for msg in REPEAT_MSG_LIST:
|
||||||
|
# await msg.delete()
|
||||||
|
await command(context=context, content=content, repeat=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
@client.command(name='list', aliases=['l'], pass_context=True)
|
||||||
|
async def list_transfers(context, *, content="", repeat=False):
|
||||||
|
global REPEAT_COMMAND, REPEAT_MSG_LIST
|
||||||
if await CommandPrecheck(context):
|
if await CommandPrecheck(context):
|
||||||
filter_by, sort_by, filter_regex = torGetListOpsFromStr(content)
|
filter_by, sort_by, filter_regex = torGetListOpsFromStr(content)
|
||||||
if filter_by == -1:
|
if filter_by == -1:
|
||||||
await context.message.channel.send("Invalid filter specified. Choose one of {}".format(str(filter_names)))
|
await context.message.channel.send("Invalid filter specified. Choose one of {}".format(str(filter_names_full)))
|
||||||
return
|
return
|
||||||
if sort_by == -1:
|
if sort_by == -1:
|
||||||
await context.message.channel.send("Invalid sort specified. Choose one of {}".format(str(sort_names)))
|
await context.message.channel.send("Invalid sort specified. Choose one of {}".format(str(sort_names)))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not repeat:
|
||||||
|
await context.message.delete()
|
||||||
|
|
||||||
torrents = TSCLIENT.get_torrents_by(sort_by=sort_by, filter_by=filter_by, filter_regex=filter_regex)
|
torrents = TSCLIENT.get_torrents_by(sort_by=sort_by, filter_by=filter_by, filter_regex=filter_regex)
|
||||||
|
|
||||||
embeds = torList(torrents, title="{} transfer{} matching '`{}`'".format(len(torrents),'' if len(torrents)==1 else 's',content))
|
embeds = torList(torrents, title="{} transfer{} matching '`{}`'".format(len(torrents),'' if len(torrents)==1 else 's',content))
|
||||||
|
|
||||||
for e in embeds:
|
embeds[-1].set_footer(text="📜 Symbol legend{}".format('\nUpdating every {} second{}—❎ to stop'.format(REPEAT_FREQ,'s' if REPEAT_FREQ != 1 else '') if repeat else ', 🔄 to auto-update'))
|
||||||
msg = await context.message.channel.send(embed=e)
|
|
||||||
|
|
||||||
await msg.add_reaction('📜')
|
if repeat:
|
||||||
|
msgs = REPEAT_MSG_LIST
|
||||||
|
for i,e in enumerate(embeds):
|
||||||
|
if i < len(msgs):
|
||||||
|
await msgs[i].edit(embed=e)
|
||||||
|
if i < len(embeds) and len(msgs[i].reactions) > 0:
|
||||||
|
await msgs[i].clear_reactions()
|
||||||
|
else:
|
||||||
|
msgs.append(await context.message.channel.send(embed=e))
|
||||||
|
if len(msgs) > len(embeds):
|
||||||
|
for i in range(len(msgs) - len(embeds)):
|
||||||
|
await msgs[-1].delete()
|
||||||
|
del msgs[-1]
|
||||||
|
REPEAT_MSG_LIST = msgs
|
||||||
|
rxnEmoji = ['📜','❎']
|
||||||
|
else:
|
||||||
|
msgs = [await context.message.channel.send(embed=e) for e in embeds]
|
||||||
|
rxnEmoji = ['📜','🔄']
|
||||||
|
|
||||||
|
msg = msgs[-1]
|
||||||
|
|
||||||
|
msgRxns = msg.reactions
|
||||||
|
for r in msgRxns:
|
||||||
|
if str(r.emoji) not in rxnEmoji:
|
||||||
|
await msg.clear_reaction(r)
|
||||||
|
|
||||||
|
msgRxns = [str(r.emoji) for r in msgRxns]
|
||||||
|
for e in rxnEmoji:
|
||||||
|
if e not in msgRxns:
|
||||||
|
await msg.add_reaction(e)
|
||||||
|
|
||||||
def check(reaction, user):
|
def check(reaction, user):
|
||||||
return user == context.message.author and str(reaction.emoji) == '📜'
|
return user == context.message.author and reaction.message.id == msg.id and str(reaction.emoji) in rxnEmoji
|
||||||
|
|
||||||
try:
|
try:
|
||||||
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)
|
reaction, user = await client.wait_for('reaction_add', timeout=60.0 if not repeat else REPEAT_FREQ, check=check)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if str(reaction.emoji) == '📜':
|
if str(reaction.emoji) == '📜':
|
||||||
await legend(context)
|
await legend(context)
|
||||||
@client.command(name='l', pass_context=True)
|
elif str(reaction.emoji) == rxnEmoji[-1]:
|
||||||
async def l(context, *, content=""):
|
if repeat:
|
||||||
await list(context, content=content)
|
REPEAT_COMMAND = False
|
||||||
|
REPEAT_MSG_LIST = []
|
||||||
|
await context.message.channel.send("❎ Auto-update cancelled...")
|
||||||
|
else:
|
||||||
|
REPEAT_MSG_LIST = msgs
|
||||||
|
await msg.clear_reaction('🔄')
|
||||||
|
await repeat_command(list_transfers, context=context, content=content)
|
||||||
|
|
||||||
def torSummary(torrents):
|
@client.command(name='modify', aliases=['m'], pass_context=True)
|
||||||
states = ('downloading', 'seeding', 'paused', 'finished')
|
|
||||||
numInState = [len([True for t in torrents if t.status == s]) for s in states]
|
|
||||||
numTot = len(torrents)
|
|
||||||
|
|
||||||
sumTot = sum([t.totalSize for t in torrents])
|
|
||||||
totSize = humanbytes(sumTot)
|
|
||||||
totUpRate = humanbytes(sum([t.rateUpload for t in torrents]))
|
|
||||||
totDownRate = humanbytes(sum([t.rateDownload for t in torrents]))
|
|
||||||
|
|
||||||
downList = [t.progress*0.01*t.totalSize for t in torrents]
|
|
||||||
upList = [t.ratio * j for t,j in zip(torrents,downList)]
|
|
||||||
|
|
||||||
sumDown = sum(downList)
|
|
||||||
sumUp = sum(upList)
|
|
||||||
|
|
||||||
totDown = humanbytes(sumDown)
|
|
||||||
totUp = humanbytes(sumUp)
|
|
||||||
|
|
||||||
totRatio = '{:.2f}'.format(sumUp / sumDown)
|
|
||||||
|
|
||||||
totDownRatio = '{:.2f}'.format(sumDown / sumTot * 100.0)
|
|
||||||
|
|
||||||
numTopRatios = min([len(torrents),5])
|
|
||||||
topRatios = "• Top {} ratio{}:".format(numTopRatios,"s" if numTopRatios > 0 else "")
|
|
||||||
sortByRatio = sorted(torrents,key=lambda t:float(t.ratio),reverse=True)
|
|
||||||
for i in range(numTopRatios):
|
|
||||||
topRatios += "\n {:.1f} {:.35}{}".format(float(sortByRatio[i].ratio),sortByRatio[i].name,"..." if len(sortByRatio[i].name) > 35 else "")
|
|
||||||
|
|
||||||
embed=discord.Embed(title="🌊 Torrent Summary", description="*React to see list of corresponding transfers*", color=0xb51a00)
|
|
||||||
embed.set_thumbnail(url=LOGO_URL)
|
|
||||||
embed.add_field(name="⬇️ {}/s".format(totDownRate), value="⬆️ {}/s".format(totUpRate), inline=False)
|
|
||||||
embed.add_field(name="⏬ {} of {}".format(totDown,totSize), value="⏫ {} ⚖️ {}".format(totUp,totRatio), inline=False)
|
|
||||||
embed.add_field(name="🔁 {} transfer{} total".format(numTot,"" if numTot == 0 else "s"), value="🔻 {} 🌱 {} 🏁 {} ⏸ {}".format(numInState[0],numInState[1],numInState[3],numInState[2]), inline=False)
|
|
||||||
embed.set_footer(text=topRatios+"\n📜 Symbol legend")
|
|
||||||
# await context.message.channel.send(embed=embed)
|
|
||||||
return embed,numInState
|
|
||||||
|
|
||||||
@client.command(name='summary', pass_context=True)
|
|
||||||
async def summary(context, *, content=""):
|
|
||||||
if await CommandPrecheck(context):
|
|
||||||
states = ('downloading', 'seeding', 'paused', 'finished','all')
|
|
||||||
stateEmoji = ['🔻','🌱','⏸','🏁','🔃','📜']
|
|
||||||
|
|
||||||
summary=torSummary(TSCLIENT.get_torrents())
|
|
||||||
msg = await context.message.channel.send(embed=summary[0])
|
|
||||||
for i in range(len(summary[1])):
|
|
||||||
if summary[1][i] > 0:
|
|
||||||
await msg.add_reaction(stateEmoji[i])
|
|
||||||
for i in stateEmoji[-2:]:
|
|
||||||
await msg.add_reaction(i)
|
|
||||||
|
|
||||||
def check(reaction, user):
|
|
||||||
return user == context.message.author and str(reaction.emoji) in stateEmoji
|
|
||||||
|
|
||||||
try:
|
|
||||||
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if str(reaction.emoji) in stateEmoji[:-1]:
|
|
||||||
cmds = {i:j for i,j in zip(stateEmoji,('--filter downloading', '--filter seeding', '--filter stopped', '--filter finished', ''))}
|
|
||||||
await list(context, content=cmds[str(reaction.emoji)])
|
|
||||||
elif str(reaction.emoji) == stateEmoji[-1]:
|
|
||||||
await legend(context)
|
|
||||||
@client.command(name='s', pass_context=True)
|
|
||||||
async def s(context, *, content=""):
|
|
||||||
await summary(context, content=content)
|
|
||||||
|
|
||||||
def strListToList(strList):
|
|
||||||
if not re.match('^[0-9\,\-]+$', strList):
|
|
||||||
return False
|
|
||||||
outList = []
|
|
||||||
for seg in strList.strip().split(","):
|
|
||||||
subseg = seg.split("-")
|
|
||||||
if len(subseg) == 1 and int(subseg[0]) not in outList:
|
|
||||||
outList.append(int(subseg[0]))
|
|
||||||
elif len(subseg) == 2:
|
|
||||||
subseg = sorted([int(i) for i in subseg])
|
|
||||||
outList += range(subseg[0],subseg[1]+1)
|
|
||||||
if len(outList) == 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return outList
|
|
||||||
|
|
||||||
@client.command(name='modify', pass_context=True)
|
|
||||||
async def modify(context, *, content=""):
|
async def modify(context, *, content=""):
|
||||||
if await CommandPrecheck(context):
|
if await CommandPrecheck(context):
|
||||||
allOnly = content.strip() == ""
|
allOnly = content.strip() == ""
|
||||||
@ -681,27 +857,28 @@ async def modify(context, *, content=""):
|
|||||||
if not id_list:
|
if not id_list:
|
||||||
filter_by, sort_by, filter_regex = torGetListOpsFromStr(content)
|
filter_by, sort_by, filter_regex = torGetListOpsFromStr(content)
|
||||||
if filter_by == -1:
|
if filter_by == -1:
|
||||||
await context.message.channel.send("Invalid filter specified. Choose one of {}".format(str(filter_names)))
|
await context.message.channel.send("Invalid filter specified. Choose one of {}".format(str(filter_names_full)))
|
||||||
return
|
return
|
||||||
if sort_by == -1:
|
if sort_by == -1:
|
||||||
await context.message.channel.send("Invalid sort specified. Choose one of {}".format(str(sort_names)))
|
await context.message.channel.send("Invalid sort specified. Choose one of {}".format(str(sort_names)))
|
||||||
return
|
return
|
||||||
# await context.message.channel.send("{} {} {} {}".format())
|
|
||||||
# return
|
await context.message.delete()
|
||||||
|
|
||||||
torrents = TSCLIENT.get_torrents_by(filter_by=filter_by, sort_by=sort_by, filter_regex=filter_regex, id_list=id_list)
|
torrents = TSCLIENT.get_torrents_by(filter_by=filter_by, sort_by=sort_by, filter_regex=filter_regex, id_list=id_list)
|
||||||
|
|
||||||
if len(torrents) > 0:
|
if len(torrents) > 0:
|
||||||
ops = ["pause","resume","remove","removedelete"]
|
ops = ["pause","resume","remove","removedelete","verify"]
|
||||||
opNames = ["pause","resume","remove","remove and delete"]
|
opNames = ["pause","resume","remove","remove and delete","verify"]
|
||||||
opEmoji = ['⏸','▶️','❌','🗑']
|
opEmoji = ['⏸','▶️','❌','🗑','🩺']
|
||||||
opStr = "⏸pause ▶️resume ❌remove 🗑remove and delete"
|
opStr = "⏸pause ▶️resume ❌remove 🗑remove and delete 🩺verify"
|
||||||
embeds = torList(torrents,author_name="Click a reaction to choose modification".format(len(torrents), '' if len(torrents)==1 else 's'),title="{} transfer{} matching '`{}`' will be modified".format(len(torrents), '' if len(torrents)==1 else 's', content))
|
embeds = torList(torrents,author_name="Click a reaction to choose modification".format(len(torrents), '' if len(torrents)==1 else 's'),title="{} transfer{} matching '`{}`' will be modified".format(len(torrents), '' if len(torrents)==1 else 's', content))
|
||||||
# embeds[-1].set_footer(text=opStr)
|
|
||||||
else:
|
else:
|
||||||
embed=discord.Embed(title="Modify transfers",color=0xb51a00)
|
embed=discord.Embed(title="Modify transfers",color=0xb51a00)
|
||||||
embed.set_author(name="No matching transfers found!", icon_url=LOGO_URL)
|
embed.set_author(name="No matching transfers found!", icon_url=LOGO_URL)
|
||||||
embeds = [embed]
|
embeds = [embed]
|
||||||
else:
|
else:
|
||||||
|
await context.message.delete()
|
||||||
ops = ["pauseall","resumeall"]
|
ops = ["pauseall","resumeall"]
|
||||||
opNames = ["pause all","resume all"]
|
opNames = ["pause all","resume all"]
|
||||||
opEmoji = ['⏸','▶️']
|
opEmoji = ['⏸','▶️']
|
||||||
@ -732,7 +909,9 @@ async def modify(context, *, content=""):
|
|||||||
await legend(context)
|
await legend(context)
|
||||||
elif str(reaction.emoji) in opEmoji[:-1]:
|
elif str(reaction.emoji) in opEmoji[:-1]:
|
||||||
cmds = {i:j for i,j in zip(opEmoji,ops)}
|
cmds = {i:j for i,j in zip(opEmoji,ops)}
|
||||||
|
cmdNames = {i:j for i,j in zip(opEmoji,opNames)}
|
||||||
cmd = cmds[str(reaction.emoji)]
|
cmd = cmds[str(reaction.emoji)]
|
||||||
|
cmdName = cmdNames[str(reaction.emoji)]
|
||||||
|
|
||||||
doContinue = True
|
doContinue = True
|
||||||
if "remove" in cmds[str(reaction.emoji)]:
|
if "remove" in cmds[str(reaction.emoji)]:
|
||||||
@ -752,34 +931,34 @@ async def modify(context, *, content=""):
|
|||||||
else:
|
else:
|
||||||
doContinue = str(reaction.emoji) == '✅'
|
doContinue = str(reaction.emoji) == '✅'
|
||||||
if doContinue:
|
if doContinue:
|
||||||
|
await context.message.channel.send("{} Trying to {} transfer{}, please wait...".format(str(reaction.emoji), cmdName, 's' if allOnly or len(torrents) > 1 else ''))
|
||||||
if "pause" in cmd:
|
if "pause" in cmd:
|
||||||
stop_torrents(torrents)
|
stop_torrents(torrents)
|
||||||
elif "resume" in cmd:
|
elif "resume" in cmd:
|
||||||
resume_torrents(torrents)
|
resume_torrents(torrents)
|
||||||
|
elif "verify" in cmd:
|
||||||
|
verify_torrents(torrents)
|
||||||
else:
|
else:
|
||||||
remove_torrents(torrents,delete_files="delete" in cmd)
|
remove_torrents(torrents,delete_files="delete" in cmd)
|
||||||
|
|
||||||
ops = ["pause","resume","remove","removedelete","pauseall","resumeall"]
|
ops = ["pause","resume","remove","removedelete","pauseall","resumeall","verify"]
|
||||||
opNames = ["paused","resumed","removed","removed and deleted","paused","resumed"]
|
opNames = ["paused","resumed","removed","removed and deleted","paused","resumed","queued for verification"]
|
||||||
opEmoji = ["⏸","▶️","❌","🗑","⏸","▶️"]
|
opEmoji = ["⏸","▶️","❌","🗑","⏸","▶️","🩺"]
|
||||||
ops = {i:j for i,j in zip(ops,opNames)}
|
ops = {i:j for i,j in zip(ops,opNames)}
|
||||||
opEmoji = {i:j for i,j in zip(ops,opEmoji)}
|
opEmoji = {i:j for i,j in zip(ops,opEmoji)}
|
||||||
await context.message.channel.send("{} Transfer{} {}".format(str(reaction.emoji),'s' if allOnly or len(torrents) > 1 else '', ops[cmd]))
|
await context.message.channel.send("{} Transfer{} {}".format(str(reaction.emoji),'s' if allOnly or len(torrents) > 1 else '', ops[cmd]))
|
||||||
else:
|
else:
|
||||||
await context.message.channel.send("❌ Cancelled!")
|
await context.message.channel.send("❌ Cancelled!")
|
||||||
@client.command(name='m', pass_context=True)
|
|
||||||
async def m(context, *, content=""):
|
|
||||||
await modify(context, content=content)
|
|
||||||
|
|
||||||
@client.command(name='legend', pass_context=True)
|
@client.command(name='legend', pass_context=True)
|
||||||
async def legend(context):
|
async def legend(context):
|
||||||
embed = discord.Embed(title='Symbol legend', color=0xb51a00)
|
embed = discord.Embed(title='Symbol legend', color=0xb51a00)
|
||||||
embed.add_field(name="Status", value="🔻—downloading\n🌱—seeding\n🏁—finished\n⏸—paused\n🔁—any", inline=True)
|
embed.add_field(name="Status", value="🔻—downloading\n🌱—seeding\n⏸—paused\n🩺—verifying\n🚧—queued\n🏁—finished\n↕️—any", inline=True)
|
||||||
embed.add_field(name="Error", value="✅—none\n⚠️—tracker warning\n🌐—tracker error\n🖥—local error", inline=True)
|
embed.add_field(name="Error", value="✅—none\n⚠️—tracker warning\n🌐—tracker error\n🖥—local error", inline=True)
|
||||||
embed.add_field(name="Metrics", value="⬇️—(total) download rate\n⬆️—(total) upload rate\n⏬—total downloaded\n⏫—total uploaded\n⚖️—seed ratio", inline=True)
|
embed.add_field(name="Metrics", value="⬇️—download rate\n⬆️—upload rate\n⏬—total downloaded\n⏫—total uploaded\n⚖️—seed ratio", inline=True)
|
||||||
embed.add_field(name="Timeout", value="🐢—stalled\n🐇—running", inline=True)
|
embed.add_field(name="Activity", value="🐢—stalled\n🐇—active\n🚀—running (rate>0)", inline=True)
|
||||||
embed.add_field(name="Tracker", value="🔒—private\n🔓—public", inline=True)
|
embed.add_field(name="Tracker", value="🔒—private\n🔓—public", inline=True)
|
||||||
embed.add_field(name="Modifications", value="⏸—pause\n▶️—resume\n❌—remove\n🗑—remove and delete", inline=True)
|
embed.add_field(name="Modifications", value="⏸—pause\n▶️—resume\n❌—remove\n🗑—remove and delete\n🩺—verify", inline=True)
|
||||||
await context.message.channel.send(embed=embed)
|
await context.message.channel.send(embed=embed)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -793,7 +972,7 @@ async def help(context, *, content=""):
|
|||||||
embed = discord.Embed(title='List transfers', color=0xb51a00)
|
embed = discord.Embed(title='List transfers', color=0xb51a00)
|
||||||
embed.set_author(name="List current transfers with sorting, filtering, and search options", icon_url=LOGO_URL)
|
embed.set_author(name="List current transfers with sorting, filtering, and search options", icon_url=LOGO_URL)
|
||||||
embed.add_field(name="Usage", value='`{0}list [--filter FILTER] [--sort SORT] [NAME]`'.format(BOT_PREFIX), inline=False)
|
embed.add_field(name="Usage", value='`{0}list [--filter FILTER] [--sort SORT] [NAME]`'.format(BOT_PREFIX), inline=False)
|
||||||
embed.add_field(name="Filtering", value='`--filter FILTER` or `-f FILTER`\n`FILTER` is one of `{}`'.format(str(filter_names)), inline=False)
|
embed.add_field(name="Filtering", value='`--filter FILTER` or `-f FILTER`\n`FILTER` is one of `{}`'.format(str(filter_names_full)), inline=False)
|
||||||
embed.add_field(name="Sorting", value='`--sort SORT` or `-s SORT`\n`SORT` is one of `{}`'.format(str(sort_names)), inline=False)
|
embed.add_field(name="Sorting", value='`--sort SORT` or `-s SORT`\n`SORT` is one of `{}`'.format(str(sort_names)), inline=False)
|
||||||
embed.add_field(name="Searching by name", value='`NAME` is a regular expression used to search transfer names (no enclosing quotes; may contain spaces)', inline=False)
|
embed.add_field(name="Searching by name", value='`NAME` is a regular expression used to search transfer names (no enclosing quotes; may contain spaces)', inline=False)
|
||||||
embed.add_field(name="Examples", value="*List all transfers:* `{0}list`\n*Search using phrase 'ubuntu':* `{0}l ubuntu`\n*List downloading transfers:* `{0}l -f downloading`\n*Sort transfers by age:* `{0}list --sort age`".format(BOT_PREFIX), inline=False)
|
embed.add_field(name="Examples", value="*List all transfers:* `{0}list`\n*Search using phrase 'ubuntu':* `{0}l ubuntu`\n*List downloading transfers:* `{0}l -f downloading`\n*Sort transfers by age:* `{0}list --sort age`".format(BOT_PREFIX), inline=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user