bugfixes and performance enhancements

* disabled checking reactions as their added; that was slow.
* reload transmissionrpc client (when there's no repeating messages) so the bot can handle when transmission restarts or becomes unavailable.
This commit is contained in:
Tim Wilson 2020-09-05 01:31:43 -06:00 committed by GitHub
parent d6cd84dbcf
commit 8442665ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

127
bot.py
View File

@ -83,6 +83,7 @@ REPEAT_MSGS = {}
client = Bot(command_prefix=BOT_PREFIX) client = Bot(command_prefix=BOT_PREFIX)
TSCLIENT = None TSCLIENT = None
MAKE_CLIENT_FAILED = False
logger = logging.getLogger('transmission_bot') logger = logging.getLogger('transmission_bot')
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
@ -135,7 +136,7 @@ class TSClient(transmissionrpc.Client):
helper functionality. helper functionality.
""" """
def get_torrents_by(self, sort_by=None, filter_by=None, reverse=False, filter_regex=None, id_list=None): def get_torrents_by(self, sort_by=None, filter_by=None, reverse=False, filter_regex=None, id_list=None, num_results=None):
"""This method will call get_torrents and then perform any sorting or filtering """This method will call get_torrents and then perform any sorting or filtering
actions requested on the returned torrent set. actions requested on the returned torrent set.
@ -147,12 +148,13 @@ class TSClient(transmissionrpc.Client):
:return: Sorted and filter torrent list :return: Sorted and filter torrent list
:rtype: transmissionrpc.Torrent[] :rtype: transmissionrpc.Torrent[]
""" """
if id_list:
torrents = self.get_torrents(ids=id_list)
else:
torrents = self.get_torrents() torrents = self.get_torrents()
if filter_regex: if filter_regex:
regex = re.compile(filter_regex, re.IGNORECASE) regex = re.compile(filter_regex, re.IGNORECASE)
torrents = [tor for tor in torrents if regex.search(tor.name)] torrents = [tor for tor in torrents if regex.search(tor.name)]
if id_list:
torrents = [tor for tor in torrents if tor.id in id_list]
if filter_by: if filter_by:
for f in filter_by.split(): for f in filter_by.split():
if f in filter_names: if f in filter_names:
@ -180,6 +182,8 @@ class TSClient(transmissionrpc.Client):
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)
if num_results and num_results < len(torrents):
torrents = torrents[-num_results:]
return torrents return torrents
def make_client(): def make_client():
@ -191,16 +195,26 @@ def make_client():
:param args: Optional CLI args passed in. :param args: Optional CLI args passed in.
:return: :return:
""" """
global MAKE_CLIENT_FAILED
try: try:
return TSClient( tsclient = TSClient(
TSCLIENT_CONFIG['host'], TSCLIENT_CONFIG['host'],
port=TSCLIENT_CONFIG['port'], port=TSCLIENT_CONFIG['port'],
user=TSCLIENT_CONFIG['user'], user=TSCLIENT_CONFIG['user'],
password=TSCLIENT_CONFIG['password'] password=TSCLIENT_CONFIG['password']
) )
MAKE_CLIENT_FAILED = False
return tsclient
except: except:
MAKE_CLIENT_FAILED = True
return None return None
def reload_client():
global TSCLIENT
TSCLIENT = make_client()
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"""
@ -399,7 +413,7 @@ def stop_torrents(torrents=[], reason=DEFAULT_REASON):
TSCLIENT.stop_torrent(torrent.hashString) TSCLIENT.stop_torrent(torrent.hashString)
logger.info("Paused: {} {}\n\tReason: {}\n\tDry run: {}".format(torrent.name, torrent.hashString, reason, DRYRUN)) logger.info("Paused: {} {}\n\tReason: {}\n\tDry run: {}".format(torrent.name, torrent.hashString, reason, DRYRUN))
def resume_torrents(torrents=[], reason=DEFAULT_REASON): def resume_torrents(torrents=[], reason=DEFAULT_REASON, start_all=False):
""" Stop (pause) a list of torrents from the client. """ Stop (pause) a list of torrents from the client.
:param client: Transmission RPC Client :param client: Transmission RPC Client
@ -412,6 +426,11 @@ def resume_torrents(torrents=[], reason=DEFAULT_REASON):
:type dry_run: bool :type dry_run: bool
:return: :return:
""" """
if start_all:
if not DRYRUN:
TSCLIENT.start_all()
logger.info("Resumed: all transfers\n\tReason: {}\n\tDry run: {}".format(torrent.name, torrent.hashString, reason, DRYRUN))
else:
for torrent in (torrents if len(torrents) > 0 else TSCLIENT.get_torrents()): for torrent in (torrents if len(torrents) > 0 else TSCLIENT.get_torrents()):
if torrent.status == "stopped": if torrent.status == "stopped":
if not DRYRUN: if not DRYRUN:
@ -451,8 +470,7 @@ def add_torrent(torStr):
@client.event @client.event
async def on_ready(): async def on_ready():
global TSCLIENT reload_client()
TSCLIENT = make_client()
if TSCLIENT is None: if TSCLIENT is None:
print("Failed to create transmissionrpc client") print("Failed to create transmissionrpc client")
else: else:
@ -578,13 +596,21 @@ async def add(context, *, content = ""):
torStr = [] torStr = []
for t in torFileList: for t in torFileList:
# await context.message.channel.send('Adding torrent from file: {}\n Please wait...'.format(t["name"])) # await context.message.channel.send('Adding torrent from file: {}\n Please wait...'.format(t["name"]))
try:
tor = add_torrent(t["content"]) tor = add_torrent(t["content"])
except:
await context.message.channel.send('‼️ Error communicating with Transmission ‼️')
return
torStr.append("From file: {}".format(tor.name)) torStr.append("From file: {}".format(tor.name))
for t in content.strip().split(" "): for t in content.strip().split(" "):
if len(t) > 5: if len(t) > 5:
# await context.message.channel.send('Adding torrent from link: {}\n Please wait...'.format(t)) # await context.message.channel.send('Adding torrent from link: {}\n Please wait...'.format(t))
try:
tor = add_torrent(t) tor = add_torrent(t)
except:
await context.message.channel.send('‼️ Error communicating with Transmission ‼️')
return
torStr.append("From link: {}".format(tor.name)) torStr.append("From link: {}".format(tor.name))
if len(torStr) > 0: if len(torStr) > 0:
@ -705,6 +731,8 @@ async def summary(context, *, content="", repeat_msg_key=None):
global REPEAT_MSGS global REPEAT_MSGS
if await CommandPrecheck(context): if await CommandPrecheck(context):
if not repeat_msg_key: if not repeat_msg_key:
if len(REPEAT_MSGS) == 0:
reload_client()
try: try:
await context.message.delete() await context.message.delete()
except: except:
@ -746,6 +774,25 @@ async def summary(context, *, content="", repeat_msg_key=None):
await msg.add_reaction(stateEmoji[i+stateEmojiFilterStartNum]) await msg.add_reaction(stateEmoji[i+stateEmojiFilterStartNum])
elif summaryData[1][i] == 0 and stateEmoji[i+stateEmojiFilterStartNum] in msgRxns: elif summaryData[1][i] == 0 and stateEmoji[i+stateEmojiFilterStartNum] in msgRxns:
await msg.clear_reaction(stateEmoji[i+stateEmojiFilterStartNum]) await msg.clear_reaction(stateEmoji[i+stateEmojiFilterStartNum])
# if not repeat_msg_key:
# 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 in WHITELIST:
# if str(r.emoji) == '📜':
# await msg.clear_reactions()
# await legend(context)
# return
# elif str(r.emoji) == '🔄':
# await msg.clear_reaction('🔄')
# await repeat_command(summary, context=context, content=content, msg_list=[msg])
# return
# elif str(r.emoji) in stateEmoji[stateEmojiFilterStartNum-1:] and user.id == context.message.author.id:
# await msg.clear_reactions()
# await list_transfers(context, content=torStateFilters[str(r.emoji)])
# return
cache_msg = await context.message.channel.fetch_message(msg.id) cache_msg = await context.message.channel.fetch_message(msg.id)
for r in cache_msg.reactions: for r in cache_msg.reactions:
if r.count > 1: if r.count > 1:
@ -767,7 +814,10 @@ async def summary(context, *, content="", repeat_msg_key=None):
await repeat_command(summary, context=context, content=content, msg_list=[msg]) await repeat_command(summary, context=context, content=content, msg_list=[msg])
return return
elif str(r.emoji) in stateEmoji[stateEmojiFilterStartNum-1:] and user.id == context.message.author.id: elif str(r.emoji) in stateEmoji[stateEmojiFilterStartNum-1:] and user.id == context.message.author.id:
if repeat_msg_key:
await msg.clear_reaction(str(r.emoji)) await msg.clear_reaction(str(r.emoji))
else:
await msg.clear_reactions()
await list_transfers(context, content=torStateFilters[str(r.emoji)]) await list_transfers(context, content=torStateFilters[str(r.emoji)])
return return
@ -783,11 +833,17 @@ async def summary(context, *, content="", repeat_msg_key=None):
pass pass
else: else:
if str(reaction.emoji) in stateEmoji[stateEmojiFilterStartNum-1:] and str(reaction.emoji) not in ignoreEmoji: if str(reaction.emoji) in stateEmoji[stateEmojiFilterStartNum-1:] and str(reaction.emoji) not in ignoreEmoji:
if repeat_msg_key:
await msg.clear_reaction(str(reaction.emoji)) await msg.clear_reaction(str(reaction.emoji))
else:
await msg.clear_reactions()
await list_transfers(context, content=torStateFilters[str(reaction.emoji)]) await list_transfers(context, content=torStateFilters[str(reaction.emoji)])
return return
elif str(reaction.emoji) == '📜': elif str(reaction.emoji) == '📜':
if repeat_msg_key:
await msg.clear_reaction('📜') await msg.clear_reaction('📜')
else:
await msg.clear_reactions()
await legend(context) await legend(context)
return return
elif str(reaction.emoji) == '': elif str(reaction.emoji) == '':
@ -813,8 +869,8 @@ async def summary(context, *, content="", repeat_msg_key=None):
await legend(context) await legend(context)
return return
elif str(r.emoji) == '': elif str(r.emoji) == '':
await msg.clear_reactions()
REPEAT_MSGS[repeat_msg_key]['do_repeat'] = False REPEAT_MSGS[repeat_msg_key]['do_repeat'] = False
await msg.clear_reactions()
return return
elif str(r.emoji) == '🖨': elif str(r.emoji) == '🖨':
await msg.clear_reaction('🖨') await msg.clear_reaction('🖨')
@ -915,6 +971,7 @@ def torList(torrents, author_name="Torrent Transfers",title=None,description=Non
def torGetListOpsFromStr(listOpStr): def torGetListOpsFromStr(listOpStr):
filter_by = None filter_by = None
sort_by = None sort_by = None
num_results = None
splitcontent = listOpStr.split(" ") splitcontent = listOpStr.split(" ")
if "--filter" in splitcontent: if "--filter" in splitcontent:
@ -943,16 +1000,28 @@ def torGetListOpsFromStr(listOpStr):
del splitcontent[ind+1] del splitcontent[ind+1]
del splitcontent[ind] del splitcontent[ind]
if "-N" in splitcontent:
ind = splitcontent.index("-N")
if len(splitcontent) > ind + 1:
try:
num_results = int(splitcontent[ind+1])
except:
num_results = -1
del splitcontent[ind+1]
del splitcontent[ind]
filter_regex = " ".join(splitcontent).strip() filter_regex = " ".join(splitcontent).strip()
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_full: if filter_by is not None and filter_by not in filter_names_full:
return -1, None, None return -1, None, 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, None
if num_results is not None and num_results <= 0:
return None, None, None, -1
return filter_by, sort_by, filter_regex return filter_by, sort_by, filter_regex, num_results
async def repeat_command(command, context, content="", msg_list=[]): async def repeat_command(command, context, content="", msg_list=[]):
global REPEAT_MSGS global REPEAT_MSGS
@ -1001,24 +1070,28 @@ async def list_transfers(context, *, content="", repeat_msg_key=None):
filter_by = None filter_by = None
sort_by = None sort_by = None
filter_regex = None filter_regex = None
num_results = None
if not id_list: if not id_list:
filter_by, sort_by, filter_regex = torGetListOpsFromStr(content) filter_by, sort_by, filter_regex, num_results = torGetListOpsFromStr(content)
if filter_by == -1: if filter_by is not None and filter_by == -1:
await context.message.channel.send("Invalid filter specified. Choose one of {}".format(str(filter_names_full))) 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 is not None and 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 num_results is not None and num_results <= 0:
await context.message.channel.send("Must specify integer greater than 0 for `-N`!")
return
if not repeat_msg_key: if not repeat_msg_key:
if len(REPEAT_MSGS) == 0:
reload_client()
try: try:
await context.message.delete() await context.message.delete()
except: except:
pass pass
torrents = TSCLIENT.get_torrents_by(sort_by=sort_by, filter_by=filter_by, filter_regex=filter_regex, id_list=id_list) torrents = TSCLIENT.get_torrents_by(sort_by=sort_by, filter_by=filter_by, filter_regex=filter_regex, id_list=id_list, num_results=num_results)
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))
@ -1127,21 +1200,27 @@ async def modify(context, *, content=""):
filter_by = None filter_by = None
sort_by = None sort_by = None
filter_regex = None filter_regex = None
num_results = None
if not id_list: if not id_list:
filter_by, sort_by, filter_regex = torGetListOpsFromStr(content) filter_by, sort_by, filter_regex, num_results = torGetListOpsFromStr(content)
if filter_by == -1: if filter_by is not None and filter_by == -1:
await context.message.channel.send("Invalid filter specified. Choose one of {}".format(str(filter_names_full))) 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 is not None and 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 num_results is not None and num_results <= 0:
await context.message.channel.send("Must specify integer greater than 0 for `-N`!")
return
try: try:
await context.message.delete() await context.message.delete()
except: except:
pass pass
torrents = TSCLIENT.get_torrents_by(filter_by=filter_by, sort_by=sort_by, filter_regex=filter_regex, id_list=id_list) if len(REPEAT_MSGS) == 0:
reload_client()
torrents = TSCLIENT.get_torrents_by(filter_by=filter_by, sort_by=sort_by, filter_regex=filter_regex, id_list=id_list, num_results=num_results)
if len(torrents) > 0: if len(torrents) > 0:
ops = ["pause","resume","remove","removedelete","verify"] ops = ["pause","resume","remove","removedelete","verify"]
@ -1177,6 +1256,7 @@ async def modify(context, *, content=""):
for i in opEmoji: for i in opEmoji:
await msgs[-1].add_reaction(i) await msgs[-1].add_reaction(i)
cache_msg = await context.message.channel.fetch_message(msg.id) cache_msg = await context.message.channel.fetch_message(msg.id)
for reaction in cache_msg.reactions: for reaction in cache_msg.reactions:
if reaction.count > 1: if reaction.count > 1:
@ -1372,11 +1452,12 @@ async def help(context, *, content=""):
if content in ["l","list"]: if content in ["l","list"]:
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] [-N NUM_RESULTS] [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_full)), 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="Specify number of results to show", value='`-N NUM_RESULTS`\n`NUM_RESULTS` is an integer greater than 0', 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*List 10 most recently added transfers (sort transfers by age and specify number):* `{0}list --sort age -N 10`".format(BOT_PREFIX), inline=False)
await context.message.channel.send(embed=embed) await context.message.channel.send(embed=embed)
elif content in ["a","add"]: elif content in ["a","add"]:
embed = discord.Embed(title='Add transfer', description="If multiple torrents are added, separate them by spaces", color=0xb51a00) embed = discord.Embed(title='Add transfer', description="If multiple torrents are added, separate them by spaces", color=0xb51a00)