mirror of
https://github.com/NohamR/TransmissionBot.git
synced 2025-05-24 00:48:59 +00:00
performance improvements
* added checks for whether an added torrent uses a private tracker. If so, the command message that added the torrent(s) is deleted (added corresponding configuration option), and a message is printed to the user to remind them to check the private tracker rules regarding sharing of torrent files * `t/add` output to use embeds * better error handling and logging * added `channel.typing()` where appropriate so users know the bot is thinking * added configuration options for overriding private torrent removal protection for the user that added the transfer
This commit is contained in:
parent
5e8f0b39cb
commit
a67fd9f9fe
159
bot.py
159
bot.py
@ -50,7 +50,9 @@ CONFIG = {
|
||||
"blacklist_user_ids": [], # discord users disallowed to use bot
|
||||
"owner_user_ids": [], # discord users given full access
|
||||
"delete_command_messages": False, # delete command messages from users
|
||||
"delete_command_message_private_torrent": True, # deletes command message if that message contains one or more torrent files that use a private tracker
|
||||
"private_transfers_protected": True, # prevent transfers on private trackers from being removed
|
||||
"private_transfer_protection_added_user_override": True, # if true, the user that added a private transfer can remove it regardless of 'private_transfers_protected'
|
||||
"whitelist_user_can_remove": True, # if true, whitelisted users can remove any transfer
|
||||
"whitelist_user_can_delete": True, # if true, whitelisted users can remove and delete any transfer
|
||||
"whitelist_added_user_remove_delete_override": True, # if true, override both 'whitelist_user_can_remove' and 'whitelist_user_can_delete' allowing whitelisted users to remove and delete transfers they added
|
||||
@ -119,6 +121,7 @@ logger.setLevel(logging.INFO) # set according to table below. values LESS than t
|
||||
|
||||
"""
|
||||
Level Numeric value
|
||||
__________________________
|
||||
CRITICAL 50
|
||||
ERROR 40
|
||||
WARNING 30
|
||||
@ -662,10 +665,14 @@ def verify_torrents(torrents=[]):
|
||||
logger.info("Verified: {} {}\n\tDry run: {}".format(torrent.name, torrent.hashString, CONFIG['dryrun']))
|
||||
|
||||
def add_torrent(torStr):
|
||||
tor = None
|
||||
torrent = None
|
||||
if not CONFIG['dryrun']:
|
||||
if torStr != "":
|
||||
tor = TSCLIENT.add_torrent(torStr)
|
||||
return tor
|
||||
torrent = TSCLIENT.add_torrent(torStr)
|
||||
logger.info("Added: {} {}\n\tDry run: {}".format(torrent.name, torrent.hashString, CONFIG['dryrun']))
|
||||
else:
|
||||
logger.info("Added: {} \n\tDry run: {}".format(torStr if len(torStr) < 300 else torStr[:200], CONFIG['dryrun']))
|
||||
return torrent
|
||||
|
||||
|
||||
# Begin discord bot functions, adapted from https://github.com/kkrypt0nn/Python-Discord-Bot-Template
|
||||
@ -813,7 +820,7 @@ def prepare_notifications(changedTransfers, states=CONFIG['notification_states']
|
||||
embeds.append(discord.Embed(title=""))
|
||||
embeds[-1].timestamp = ts
|
||||
if len(nameStr) + len(valStr) + len(v) > 1000:
|
||||
embeds[-1].add_field(name=nStr, value=valStr, inline=False)
|
||||
embeds[-1].add_field(name=nameStr, value=valStr, inline=False)
|
||||
nameStr = ""
|
||||
valStr = ""
|
||||
else:
|
||||
@ -956,7 +963,7 @@ async def loop_notifications():
|
||||
@client.event
|
||||
async def on_ready():
|
||||
global TSCLIENT_CONFIG, CONFIG
|
||||
|
||||
unlock()
|
||||
TSCLIENT_CONFIG = CONFIG['tsclient']
|
||||
if not CONFIG: # load from config file
|
||||
CONFIG = load_json(path=CONFIG_JSON)
|
||||
@ -1116,8 +1123,17 @@ def message_has_torrent_file(message):
|
||||
return True
|
||||
return False
|
||||
|
||||
def commaListToParagraphForm(l):
|
||||
outStr = ''
|
||||
if len(l) > 0:
|
||||
outStr += ('' if len(l <= 2) else ', ').join(l[:-1])
|
||||
outStr += ('{} and '.format('' if len(l) <= 2 else ',') if len(l) > 1 else '') + str(l[-1])
|
||||
|
||||
return outStr
|
||||
|
||||
async def add(message, content = ""):
|
||||
if await CommandPrecheck(message):
|
||||
async with message.channel.typing():
|
||||
torFileList = []
|
||||
for f in message.attachments:
|
||||
if len(f.filename) > 8 and f.filename[-8:].lower() == ".torrent":
|
||||
@ -1135,20 +1151,25 @@ async def add(message, content = ""):
|
||||
pass
|
||||
|
||||
torStr = []
|
||||
for t in torFileList:
|
||||
torIDs = []
|
||||
for i,t in enumerate(torFileList):
|
||||
# await message.channel.send('Adding torrent from file: {}\n Please wait...'.format(t["name"]))
|
||||
try:
|
||||
tor = add_torrent(t["content"])
|
||||
if tor:
|
||||
logger.info("User {} ({}) added torrent from file: {} ({})".format(message.author.name, message.author.id, tor.name, tor.hashString))
|
||||
lock()
|
||||
TORRENT_ADDED_USERS[tor.hashString] = message.author.id
|
||||
unlock()
|
||||
logger.info("User {} ({}) added torrent from file {}: {} ({})".format(message.author.name, message.author.id, t["name"], tor.name, tor.hashString))
|
||||
# if tor.isPrivate:
|
||||
# privateTransfers.append(len(privateTransfers))
|
||||
logger.debug("Added to TORRENT_ADDED_USERS")
|
||||
except:
|
||||
await message.channel.send('‼️ Error communicating with Transmission ‼️')
|
||||
return
|
||||
torStr.append("📄 {}".format(tor.name))
|
||||
torStr.append("💽 {}".format(tor.name))
|
||||
torIDs.append(tor.id)
|
||||
elif CONFIG['dryrun']:
|
||||
torStr.append("💽 added file dryrun: {}".format(t["name"]))
|
||||
except Exception as e:
|
||||
logger.warning("Exception when adding torrent from file: {}".format(e))
|
||||
|
||||
for t in content.strip().split(" "):
|
||||
if len(t) > 5:
|
||||
@ -1156,24 +1177,72 @@ async def add(message, content = ""):
|
||||
try:
|
||||
tor = add_torrent(t)
|
||||
if tor:
|
||||
logger.info("User {} ({}) added torrent from URL: {} ({})".format(message.author.name, message.author.id, tor.name, tor.hashString))
|
||||
lock()
|
||||
TORRENT_ADDED_USERS[tor.hashString] = message.author.id
|
||||
unlock()
|
||||
logger.info("User {} ({}) added torrent from URL: {} ({})".format(message.author.name, message.author.id, tor.name, tor.hashString))
|
||||
# if tor.isPrivate:
|
||||
# privateTransfers.append(len(privateTransfers))
|
||||
logger.debug("Added to TORRENT_ADDED_USERS")
|
||||
except:
|
||||
await message.channel.send('‼️ Error communicating with Transmission ‼️')
|
||||
return
|
||||
torStr.append("🧲 {}".format(tor.name))
|
||||
torIDs.append(tor.id)
|
||||
except Exception as e:
|
||||
logger.warning("Exception when adding torrent from URL: {}".format(e))
|
||||
|
||||
if len(torStr) > 0:
|
||||
await message.channel.send('🟢 Added torrent{}:\n{}'.format("s" if len(torStr) > 1 else "", '\n'.join(torStr)))
|
||||
embeds = []
|
||||
if len('\n'.join(torStr)) > 2000:
|
||||
embeds.append(discord.Embed(title='🟢 Added torrents'))
|
||||
descStr = torStr[0]
|
||||
for t in torStr[1:]:
|
||||
if len(descStr) + len(t) < 2000:
|
||||
descStr += '\n{}'.format(t)
|
||||
else:
|
||||
embeds[-1].description = descStr
|
||||
embeds.append(discord.Embed(title='🟢 Added torrents'))
|
||||
descStr = t
|
||||
else:
|
||||
embeds = [discord.Embed(title='🟢 Added torrent{}'.format("s" if len(torStr) > 1 else ""), description='\n'.join(torStr), color=0xb51a00)]
|
||||
privateTransfers = []
|
||||
if not CONFIG['dryrun']:
|
||||
logger.debug("Checking for private transfers amidst the {} new torrents".format(len(torStr)))
|
||||
privateCheckSuccess = False
|
||||
for i in range(5):
|
||||
try:
|
||||
newTorrents = TSCLIENT.get_torrents_by(id_list=torIDs)
|
||||
logger.debug("Fetched {} transfers from transmission corresponding to the {} transfer IDs recorded".format(len(newTorrents),len(torIDs)))
|
||||
for tor in newTorrents:
|
||||
logger.debug("Checking private status of added transfer {}: {}".format(i+1, tor.name))
|
||||
if tor.isPrivate:
|
||||
privateTransfers.append(torIDs.index(tor.id))
|
||||
logger.debug("Transfer is private")
|
||||
privateCheckSuccess = True
|
||||
logger.debug("Successfully checked for private tranfers: {} found".format(len(privateTransfers)))
|
||||
break
|
||||
except AttributeError as e:
|
||||
logger.debug("Attribute error when checking for private status of added torrent(s): {}".format(e))
|
||||
except Exception as e:
|
||||
logger.warning("Exception when checking for private status of added torrent(s): {}".format(e))
|
||||
asyncio.sleep(0.2)
|
||||
if len(privateTransfers) > 0 or CONFIG['dryrun']:
|
||||
if len(privateTransfers) > 0 and CONFIG['delete_command_message_private_torrent']:
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception as e:
|
||||
logger.warning("Exception when removing command message used to add private torrent(s): {}".format(e))
|
||||
embeds[-1].set_footer(text="\n🔐 One or more added torrents are using a private tracker, which may prohibit running the same transfer from multiple locations. Ensure that you're not breaking any private tracker rules.{}".format('' if CONFIG['dryrun'] else "\n(I erased the command message to prevent any unintentional sharing of torrent files)"))
|
||||
for e in embeds:
|
||||
await message.channel.send(embed=e)
|
||||
else:
|
||||
await message.channel.send('🚫 No torrents added!')
|
||||
|
||||
|
||||
@client.command(name='add', aliases=['a'], pass_context=True)
|
||||
async def add_cmd(context, *, content = ""):
|
||||
try:
|
||||
await add(context.message, content=content)
|
||||
except Exception as e:
|
||||
logger.warning("Exception when adding torrent(s): {}".format(e))
|
||||
|
||||
# def torInfo(t):
|
||||
# states = ('downloading', 'seeding', 'stopped', 'finished','all')
|
||||
@ -1289,6 +1358,7 @@ def torSummary(torrents, repeat_msg_key=None, show_repeat=True):
|
||||
async def summary(message, content="", repeat_msg_key=None):
|
||||
global REPEAT_MSGS
|
||||
if await CommandPrecheck(message):
|
||||
async with message.channel.typing():
|
||||
if not repeat_msg_key:
|
||||
if len(REPEAT_MSGS) == 0:
|
||||
reload_client()
|
||||
@ -1453,7 +1523,10 @@ async def summary(message, content="", repeat_msg_key=None):
|
||||
|
||||
@client.command(name='summary',aliases=['s'], pass_context=True)
|
||||
async def summary_cmd(context, *, content="", repeat_msg_key=None):
|
||||
try:
|
||||
await summary(context.message, content, repeat_msg_key=repeat_msg_key)
|
||||
except Exception as e:
|
||||
logger.warning("Exception in t/summary: {}".format(e))
|
||||
|
||||
def strListToList(strList):
|
||||
if not re.match('^[0-9\,\-]+$', strList):
|
||||
@ -1647,6 +1720,7 @@ async def repeat_command(command, message, content="", msg_list=[]):
|
||||
async def list_transfers(message, content="", repeat_msg_key=None):
|
||||
global REPEAT_MSGS
|
||||
if await CommandPrecheck(message):
|
||||
async with message.channel.typing():
|
||||
id_list = strListToList(content)
|
||||
filter_by = None
|
||||
sort_by = None
|
||||
@ -1801,10 +1875,14 @@ async def list_transfers(message, content="", repeat_msg_key=None):
|
||||
|
||||
@client.command(name='list', aliases=['l'], pass_context=True)
|
||||
async def list_transfers_cmd(context, *, content="", repeat_msg_key=None):
|
||||
try:
|
||||
await list_transfers(context.message, content=content, repeat_msg_key=repeat_msg_key)
|
||||
except Exception as e:
|
||||
logger.warning("Exception in t/list: {}".format(e))
|
||||
|
||||
async def modify(message, content=""):
|
||||
if await CommandPrecheck(message):
|
||||
async with message.channel.typing():
|
||||
allOnly = content.strip() == ""
|
||||
torrents = []
|
||||
if not allOnly:
|
||||
@ -1894,8 +1972,16 @@ async def modify(message, content=""):
|
||||
if CONFIG['private_transfers_protected']:
|
||||
removeTorrents = [t for t in torrents if not t.isPrivate]
|
||||
if len(removeTorrents) != len(torrents):
|
||||
if CONFIG['private_transfer_protection_added_user_override']:
|
||||
oldTorrents = load_json(path=TORRENT_JSON)
|
||||
removeTorrents = [t for t in torrents if not t.isPrivate or ((t.hashString in oldTorrents and oldTorrents[t.hashString]['added_user'] == message.author.id) or (t.hashString in TORRENT_ADDED_USERS and TORRENT_ADDED_USERS[t.hashString] == message.author.id))]
|
||||
if len(removeTorrents) != len(torrents):
|
||||
torrents = removeTorrents
|
||||
footerPrepend = "(I'm not allowed to remove private transfers unless they were added by you, but those you added and the public ones)\n"
|
||||
else:
|
||||
torrents = removeTorrents
|
||||
footerPrepend = "(I'm not allowed to remove private transfers, but I'll do the public ones)\n"
|
||||
|
||||
if "delete" in cmds[str(reaction.emoji)] and not CONFIG['whitelist_user_can_delete'] and message.author.id not in CONFIG['owner_user_ids']:
|
||||
# user may not be allowed to perform this operation. Check if they added any transfers, and whether the added_user_override is enabled.
|
||||
if CONFIG['whitelist_added_user_remove_delete_override']:
|
||||
@ -1954,11 +2040,13 @@ async def modify(message, content=""):
|
||||
else:
|
||||
doContinue = str(reaction.emoji) == '✅'
|
||||
if doContinue:
|
||||
async with message.channel.typing():
|
||||
await message.channel.send("{} Trying to {} transfer{}, please wait...".format(str(reaction.emoji), cmdName, 's' if allOnly or len(torrents) > 1 else ''))
|
||||
try:
|
||||
if "pause" in cmd:
|
||||
stop_torrents(torrents)
|
||||
elif "resume" in cmd:
|
||||
resume_torrents(torrents)
|
||||
resume_torrents(torrents, start_all=("all" in cmd))
|
||||
elif "verify" in cmd:
|
||||
verify_torrents(torrents)
|
||||
else:
|
||||
@ -1974,6 +2062,9 @@ async def modify(message, content=""):
|
||||
if msg2 is not None:
|
||||
await message_clear_reactions(msg2, message)
|
||||
return
|
||||
except Exception as e:
|
||||
await message.channel.send("⚠️ A problem occurred trying to modify transfer(s). You may need to try again... Sorry!".format(str(reaction.emoji), cmdName, 's' if allOnly or len(torrents) > 1 else ''))
|
||||
logger.warning("Exception in t/modify running command '{}': {}".format(cmd,e))
|
||||
else:
|
||||
await message.channel.send("❌ Cancelled!")
|
||||
await message_clear_reactions(msg, message)
|
||||
@ -2003,9 +2094,18 @@ async def modify(message, content=""):
|
||||
doContinue = True
|
||||
if "remove" in cmds[str(reaction.emoji)]:
|
||||
footerPrepend = ""
|
||||
if CONFIG['private_transfers_protected']:
|
||||
removeTorrents = [t for t in torrents if not t.isPrivate]
|
||||
if CONFIG['private_transfers_protected']:
|
||||
removeTorrents = [t for t in torrents if not t.isPrivate]
|
||||
if len(removeTorrents) != len(torrents):
|
||||
if CONFIG['private_transfer_protection_added_user_override']:
|
||||
oldTorrents = load_json(path=TORRENT_JSON)
|
||||
removeTorrents = [t for t in torrents if not t.isPrivate or ((t.hashString in oldTorrents and oldTorrents[t.hashString]['added_user'] == message.author.id) or (t.hashString in TORRENT_ADDED_USERS and TORRENT_ADDED_USERS[t.hashString] == message.author.id))]
|
||||
if len(removeTorrents) != len(torrents):
|
||||
torrents = removeTorrents
|
||||
footerPrepend = "(I'm not allowed to remove private transfers unless they were added by you, but those you added and the public ones)\n"
|
||||
else:
|
||||
torrents = removeTorrents
|
||||
footerPrepend = "(I'm not allowed to remove private transfers, but I'll do the public ones)\n"
|
||||
if "delete" in cmds[str(reaction.emoji)] and not CONFIG['whitelist_user_can_delete'] and message.author.id not in CONFIG['owner_user_ids']:
|
||||
@ -2066,18 +2166,18 @@ async def modify(message, content=""):
|
||||
else:
|
||||
doContinue = str(reaction.emoji) == '✅'
|
||||
if doContinue:
|
||||
async with message.channel.typing():
|
||||
await message.channel.send("{} Trying to {} transfer{}, please wait...".format(str(reaction.emoji), cmdName, 's' if allOnly or len(torrents) > 1 else ''))
|
||||
try:
|
||||
if "pause" in cmd:
|
||||
stop_torrents(torrents)
|
||||
elif "resume" in cmd:
|
||||
resume_torrents(torrents)
|
||||
resume_torrents(torrents, start_all=("all" in cmd))
|
||||
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 = ["⏸","▶️","❌","🗑","⏸","▶️","🔬"]
|
||||
@ -2088,6 +2188,9 @@ async def modify(message, content=""):
|
||||
if msg2 is not None:
|
||||
await message_clear_reactions(msg2, message)
|
||||
return
|
||||
except Exception as e:
|
||||
await message.channel.send("⚠️ A problem occurred trying to modify transfer(s). You may need to try again... Sorry!".format(str(reaction.emoji), cmdName, 's' if allOnly or len(torrents) > 1 else ''))
|
||||
logger.warning("Exception in t/modify running command '{}': {}".format(cmd,e))
|
||||
else:
|
||||
await message.channel.send("❌ Cancelled!")
|
||||
await message_clear_reactions(msg, message)
|
||||
@ -2099,7 +2202,11 @@ async def modify(message, content=""):
|
||||
|
||||
@client.command(name='modify', aliases=['m'], pass_context=True)
|
||||
async def modify_cmd(context, *, content=""):
|
||||
try:
|
||||
await modify(context.message, content=content)
|
||||
except Exception as e:
|
||||
logger.warning("Exception in t/modify: {}".format(e))
|
||||
|
||||
|
||||
async def toggle_compact_out(message):
|
||||
global OUTPUT_MODE
|
||||
@ -2190,6 +2297,18 @@ async def toggle_notifications(message):
|
||||
async def toggle_notifications_cmd(context):
|
||||
await toggle_notifications(context.message)
|
||||
|
||||
async def toggle_dryrun(message):
|
||||
global CONFIG
|
||||
CONFIG['dryrun'] = not CONFIG['dryrun']
|
||||
await message.channel.send("Toggled dryrun to {}".format(CONFIG['dryrun']))
|
||||
|
||||
return
|
||||
|
||||
@client.command(name='dryrun', pass_context=True)
|
||||
async def toggle_dryrun_cmd(context):
|
||||
if CommandPrecheck(context.message, whitelist=CONFIG['owner_user_ids']):
|
||||
await toggle_dryrun(context.message)
|
||||
|
||||
@client.event
|
||||
async def on_message(message):
|
||||
if message.author.id == client.user.id:
|
||||
|
Loading…
x
Reference in New Issue
Block a user