new feature and performance improvoments

Added `t/compact` command to toggle between mobile or desktop command output that applies to `t/summary`, `t/list`, `t/modify`
User can click a reaction of `t/summary` or `t/modify` while reactions are still being printed.
This commit is contained in:
Tim Wilson 2020-08-26 12:57:15 -06:00 committed by GitHub
parent f664459431
commit ba2c55f3ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

185
bot.py
View File

@ -48,6 +48,7 @@ logging.basicConfig(format='%(asctime)s %(message)s',filename=join(expanduser("~
# END USER CONFIGURATION
COMPACT_OUTPUT = False
REPEAT_COMMAND = False
REPEAT_MSG_LIST = []
REPEAT_START_TIME = 0
@ -435,7 +436,7 @@ async def on_ready():
print("Running on:", platform.system(), platform.release(), "(" + os.name + ")")
print('-------------------')
def humanbytes(B):
def humanbytes(B,d = 2):
'Return the given bytes as a human friendly KB, MB, GB, or TB string'
B = float(B)
KB = float(1024)
@ -443,16 +444,28 @@ def humanbytes(B):
GB = float(KB ** 3) # 1,073,741,824
TB = float(KB ** 4) # 1,099,511,627,776
if d <= 0:
if B < KB:
return '{0} {1}'.format(B,'B')
return '{0}B'.format(int(B))
elif KB <= B < MB:
return '{0:.2f} kB'.format(B/KB)
return '{0:d}kB'.format(int(B/KB))
elif MB <= B < GB:
return '{0:.2f} MB'.format(B/MB)
return '{0:d}MB'.format(int(B/MB))
elif GB <= B < TB:
return '{0:.2f} GB'.format(B/GB)
return '{0:d}GB'.format(int(B/GB))
elif TB <= B:
return '{0:.2f} TB'.format(B/TB)
return '{0:d}TB'.format(int(B/TB))
else:
if B < KB:
return '{0} B'.format(B)
elif KB <= B < MB:
return '{0:.{nd}f} kB'.format(B/KB, nd = d)
elif MB <= B < GB:
return '{0:.{nd}f} MB'.format(B/MB, nd = d)
elif GB <= B < TB:
return '{0:.{nd}f} GB'.format(B/GB, nd = d)
elif TB <= B:
return '{0:.{nd}f} TB'.format(B/TB, nd = d)
def tobytes(B):
'Return the number of bytes given by a string (a float followed by a space and the unit of prefix-bytes eg. "21.34 GB")'
@ -522,7 +535,7 @@ torStates = ('downloading', 'seeding', 'stopped', 'verifying', 'queued', 'finish
)
torStateEmoji = ('🔻','🌱','','🩺','🚧','🏁',
'🐢','🐇','🚀',
'🔒','🔓',
'🔐','🔓',
'‼️','','⚠️','🌐','🖥'
)
torStateFilters = {i:"--filter {}".format(j) for i,j in zip(torStateEmoji,torStates)}
@ -592,9 +605,12 @@ def torSummary(torrents, repeat=False):
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)
if COMPACT_OUTPUT:
embed.add_field(name=' '.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[11:], numInState[11:])]), value=' '.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[6:9], numInState[6:9])]) + "" + ' '.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[9:11], numInState[9:11])]), inline=False)
else:
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=not COMPACT_OUTPUT)
embed.add_field(name="Activity", value='\n'.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[6:9], numInState[6:9])]), inline=not COMPACT_OUTPUT)
embed.add_field(name="Tracker", value='\n'.join(['{} {}'.format(i,j) for i,j in zip(torStateEmoji[9:11], numInState[9:11])]), inline=not COMPACT_OUTPUT)
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)
@ -629,7 +645,7 @@ async def summary(context, *, content="", repeat=False):
cache_msg = await context.message.channel.fetch_message(msg.id)
msgRxns = [str(r.emoji) for r in cache_msg.reactions]
for i in stateEmoji[:2]:
for i in stateEmoji[:3]:
if i not in msgRxns:
await msg.add_reaction(i)
for i in range(len(summaryData[1])):
@ -637,6 +653,58 @@ async def summary(context, *, content="", repeat=False):
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])
cache_msg = await context.message.channel.fetch_message(msg.id)
for r in cache_msg.reactions:
if r.count > 1:
async for user in r.users():
if user.id == context.message.author.id:
if str(r.emoji) == stateEmoji[0]:
await legend(context)
return
elif str(r.emoji) == stateEmoji[1]:
if repeat:
REPEAT_COMMAND = False
REPEAT_MSG_LIST = []
await context.message.channel.send("❎ Auto-update cancelled...")
return
else:
await msg.clear_reaction('🔄')
await repeat_command(summary, context=context, content=content, msg_list=[msg])
return
elif str(r.emoji) in stateEmoji[2:]:
if repeat:
REPEAT_COMMAND = False
REPEAT_MSG_LIST = []
await context.message.channel.send("❎ Auto-update cancelled...")
await list_transfers(context, content=torStateFilters[str(r.emoji)])
return
# first check to see if a user clicked a reaction before they finished printing
# cache_msg = await context.message.channel.fetch_message(msg.id)
# for r in cache_msg.reactions:
# if r.count > 1:
# async for user in r.users():
# if user.id == context.message.author.id:
# if str(r.emoji) == stateEmoji[0]:
# await legend(context)
# return
# elif str(r.emoji) == stateEmoji[1]:
# if repeat:
# REPEAT_COMMAND = False
# REPEAT_MSG_LIST = []
# await context.message.channel.send("❎ Auto-update cancelled...")
# return
# else:
# await msg.clear_reaction('🔄')
# await repeat_command(summary, context=context, content=content, msg_list=[msg])
# return
# elif str(r.emoji) in stateEmoji[2:]:
# if repeat:
# REPEAT_COMMAND = False
# REPEAT_MSG_LIST = []
# await context.message.channel.send("❎ Auto-update cancelled...")
# await list_transfers(context, content=torStateFilters[str(r.emoji)])
# return
def check(reaction, user):
return user == context.message.author and reaction.message.id == msg.id and str(reaction.emoji) in stateEmoji
@ -708,22 +776,36 @@ def torList(torrents, author_name="Torrent Transfers",title=None,description=Non
errorStrs = ['','⚠️','🌐','🖥']
def torListLine(t):
down = humanbytes(t.progress * 0.01 * t.totalSize)
out = "{}{}{}{} ".format(stateEmoji[t.status],errorStrs[t.error],'🚀' if t.rateDownload + t.rateUpload > 0 else '🐢' if t.isStalled else '🐇', '🔒' if t.isPrivate else '🔓')
if COMPACT_OUTPUT:
down = humanbytes(t.progress * 0.01 * t.totalSize, d=0)
out = "{}{} ".format(stateEmoji[t.status],errorStrs[t.error])
if t.status == 'downloading':
out += "{}/{} ⬇️ {}/s ⬆️ {}/s ⚖️ {:.2f}".format(down,humanbytes(t.totalSize),humanbytes(t.rateDownload),humanbytes(t.rateUpload),t.uploadRatio)
out += "{}%{} {}/s:*{}/s*:{:.1f}".format(int(t.progress), down, humanbytes(t.rateDownload, d=0), humanbytes(t.rateUpload, d=0), t.uploadRatio)
elif t.status == 'seeding':
out += "{} ⬆️ {}/s ⚖️ {:.2f}".format(humanbytes(t.totalSize),humanbytes(t.rateUpload),t.uploadRatio)
out += "{} *{}/s*:{:.1f}".format(down, humanbytes(t.rateUpload, d=0), t.uploadRatio)
elif t.status == 'stopped':
out += "{}/{} ⚖️ {:.2f}".format(down,humanbytes(t.totalSize),t.uploadRatio)
out += "{}%{} {:.1f}".format(int(t.progress), down, t.uploadRatio)
elif t.status == 'finished':
out += "{} ⚖️ {:.2f}".format(humanbytes(t.totalSize),t.uploadRatio)
out += "{} {:.1f}".format(down, t.uploadRatio)
else:
down = humanbytes(t.progress * 0.01 * t.totalSize)
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':
out += "{}/{} ({:.1f}%) ⬇️ {}/s ⬆️ *{}/s* ⚖️ *{:.2f}*".format(down,humanbytes(t.totalSize),t.progress, humanbytes(t.rateDownload),humanbytes(t.rateUpload),t.uploadRatio)
elif t.status == 'seeding':
out += "{} ⬆️ *{}/s* ⚖️ *{:.2f}*".format(humanbytes(t.totalSize),humanbytes(t.rateUpload),t.uploadRatio)
elif t.status == 'stopped':
out += "{}/{} ({:.1f}%) ⚖️ *{:.2f}*".format(down,humanbytes(t.totalSize),t.progress,t.uploadRatio)
elif t.status == 'finished':
out += "{} ⚖️ {:.2f}".format(humanbytes(t.totalSize),t.uploadRatio)
if t.error != 0:
out += "\n**Error:** *{}*".format(t.errorString)
out += "***Error:*** *{}*".format(t.errorString)
return out
if COMPACT_OUTPUT:
nameList = ["{}){:.26}{}".format(t.id,t.name,"..." if len(t.name) > 26 else "") for t in torrents]
else:
nameList = ["{}) {:.245}{}".format(t.id,t.name,"..." if len(t.name) > 245 else "") for t in torrents]
valList = [torListLine(t) for t in torrents]
@ -887,6 +969,7 @@ async def list_transfers(context, *, content="", repeat=False):
REPEAT_COMMAND = False
REPEAT_MSG_LIST = []
await context.message.channel.send("❎ Auto-update cancelled...")
return
else:
await msg.clear_reaction('🔄')
await repeat_command(list_transfers, context=context, content=content, msg_list=msgs)
@ -958,8 +1041,61 @@ async def modify(context, *, content=""):
opEmoji.append('📜')
msg = msgs[-1]
for i in opEmoji:
await msgs[-1].add_reaction(i)
cache_msg = await context.message.channel.fetch_message(msg.id)
for reaction in cache_msg.reactions:
if reaction.count > 1:
async for user in reaction.users():
if user.id == context.message.author.id:
if str(reaction.emoji) == opEmoji[-1]:
await legend(context)
elif str(reaction.emoji) in opEmoji[:-1]:
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)]
cmdName = cmdNames[str(reaction.emoji)]
doContinue = True
if "remove" in cmds[str(reaction.emoji)]:
embed=discord.Embed(title="Are you sure you wish to remove{} {} transfer{}?".format(' and DELETE' if 'delete' in cmds[str(reaction.emoji)] else '', len(torrents), '' if len(torrents)==1 else 's'),description="**This action is irreversible!**",color=0xb51a00)
embed.set_footer(text="react ✅ to continue or ❌ to cancel")
msg = await context.message.channel.send(embed=embed)
for i in ['','']:
await msg.add_reaction(i)
def check1(reaction, user):
return user == context.message.author and str(reaction.emoji) in ['','']
try:
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check1)
except asyncio.TimeoutError:
doContinue = False
else:
doContinue = str(reaction.emoji) == ''
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:
stop_torrents(torrents)
elif "resume" in cmd:
resume_torrents(torrents)
elif "verify" in cmd:
verify_torrents(torrents)
else:
remove_torrents(torrents,delete_files="delete" in cmd)
ops = ["pause","resume","remove","removedelete","pauseall","resumeall","verify"]
opNames = ["paused","resumed","removed","removed and deleted","paused","resumed","queued for verification"]
opEmoji = ["","▶️","","🗑","","▶️","🩺"]
ops = {i:j for i,j in zip(ops,opNames)}
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]))
return
else:
await context.message.channel.send("❌ Cancelled!")
return
def check(reaction, user):
return user == context.message.author and str(reaction.emoji) in opEmoji
@ -1016,6 +1152,14 @@ async def modify(context, *, content=""):
await context.message.channel.send("❌ Cancelled!")
return
@client.command(name='compact', aliases=['c'], pass_context=True)
async def toggle_compact_out(context):
global COMPACT_OUTPUT
COMPACT_OUTPUT = not COMPACT_OUTPUT
outStr = ''
await context.message.channel.send('📱 Switched to mobile output' if COMPACT_OUTPUT else '🖥 Switched to desktop output')
return
@client.command(name='legend', pass_context=True)
async def legend(context):
embed = discord.Embed(title='Symbol legend', color=0xb51a00)
@ -1023,7 +1167,7 @@ async def legend(context):
embed.add_field(name="Error", value="✅—none\n—tracker  warning\n🌐—tracker  error\n🖥—local  error", 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="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\n🩺—verify", inline=True)
await context.message.channel.send(embed=embed)
if REPEAT_COMMAND:
@ -1067,6 +1211,7 @@ async def help(context, *, content=""):
embed.add_field(name="List torrent transfers", value="*list current transfers with sorting, filtering, and search options*\n*ex.* `{0}list [OPTIONS]` or `{0}l [OPTIONS]`".format(BOT_PREFIX), inline=False)
embed.add_field(name="Add new torrent transfers", value="*add one or more specified torrents by magnet link or url to torrent file*\n*ex.* `{0}add TORRENT ...` or `{0}a TORRENT ...`".format(BOT_PREFIX), inline=False)
embed.add_field(name="Modify existing transfers", value="*pause, resume, remove, or remove and delete specified transfers*\n*ex.* `{0}modify [TORRENT]` or `{0}m [TORRENT]`".format(BOT_PREFIX), inline=False)
embed.add_field(name='Toggle output style', value='*toggle between desktop (default) and mobile (narrow) output style*\n*ex.* `{0}compact` or {0}c'.format(BOT_PREFIX), inline=False)
embed.add_field(name='Show legend', value='*prints legend showing the meaning of symbols used in the output of other commands*\n*ex.* `{0}legend`'.format(BOT_PREFIX), inline=False)
embed.add_field(name='Help - Gives this menu', value='*with optional details of specified command*\n*ex.* `{0}help` or `{0}help COMMAND`'.format(BOT_PREFIX), inline=False)