add tel searchrecent and tel delrecent commands

these look through all discord channels bridged to the given channel
(including that channel itself) and search for messages matching
the given query. the delrecent command, after asking for confirmation,
will then delete all of those messages that it can across all the channels.
it is capable of deleting messages sent by our own webhooks even if
we don't have manage message permissions in the channel in question.

the user interface is not amazing but these are both extpriv-only and
only send any outputs in dms so this probably isn't very important.
This commit is contained in:
ubq323 2022-01-18 23:56:11 +00:00
parent 31491d7ba9
commit 3c3ffbb036
3 changed files with 94 additions and 5 deletions

View File

@ -78,4 +78,4 @@ def setup(bot):
if ext == "all":
for ext in util.extensions: bot.reload_extension(ext)
else: bot.reload_extension(ext)
await ctx.send("Done!")
await ctx.send("Done!")

View File

@ -4,10 +4,12 @@ import logging
import asyncio
import re
import hashlib
from datetime import datetime
from datetime import datetime, timedelta
import os
import pydot
import tempfile
import collections
import aiohttp
import util
import eventbus
@ -179,6 +181,88 @@ When you want to end a call, use hangup.
await ctx.send(f"Successfully deleted.")
pass
async def _find_recent(self, chs, query):
one_week = timedelta(seconds=60*60*24*7)
one_week_ago = datetime.now()-one_week
found = collections.defaultdict(list)
async def find_msgs(ch):
# the parameters to history() here might need to be tweaked
# somewhat, for more general usage
async for msg in ch.history(limit=None,after=one_week_ago):
if query in msg.content.lower():
found[ch].append(msg)
await asyncio.gather(*(find_msgs(ch) for ch in chs))
return found
@telephone.command(brief="find recent messages in channels linked to this")
@commands.check(util.extpriv_check)
async def searchrecent(self, ctx, ch: discord.TextChannel, *, query):
author = ctx.author
chs = []
for dest in eventbus.find_all_destinations(('discord',ch.id)):
if dest[0] == 'discord':
chs.append(self.bot.get_channel(dest[1]))
found = await self._find_recent(chs, query)
out = ""
for ch,ms in found.items():
out += f"{ch.mention} (`#{ch.name}` in `{ch.guild.name}`)\n"
for m in ms:
u = m.author.name if m.author else None
w = "[WH]" if m.webhook_id else ""
out += f"- {m.content[:20]} @{m.created_at} by {u} {w}\n"
for c in util.chunks(out,2000):
await author.send(c)
return found
@telephone.command(brief="delete recent messages in channels linked to this")
@commands.check(util.extpriv_check)
async def delrecent(self, ctx, ch: discord.TextChannel, *, query):
author = ctx.author
found = await self.searchrecent(ctx,ch,query=query)
await author.send("please say 'GO' to confirm or wait 10 seconds to not confirm")
try:
await self.bot.wait_for('message',check=lambda m:m.author == ctx.author and m.content == "GO" and m.channel == ctx.author.dm_channel,timeout=10)
except asyncio.TimeoutError:
await author.send("timed out")
return
async def try_delete(msg,session):
if msg.webhook_id is not None:
# note: assumes there is only one webhook we control per channel
# i think that's the case
wh_url = await self.bot.database.execute_fetchone("SELECT webhook FROM discord_webhooks WHERE channel_id = ?",(msg.channel.id,))
if wh_url is None:
await author.send(f"no access to webhook: {msg.id} {msg.channel.mention} {msg.jump_url}")
return
wh_url = wh_url['webhook']
wh = discord.Webhook.from_url(wh_url,session=session)
await wh.delete_message(msg.id)
else:
try:
await msg.delete()
except discord.errors.Forbidden:
await author.send(f"!!! couldn't delete msg {msg.id} in {msg.channel.mention}")
msgs = []
for q in found.values():
msgs.extend(q)
async with aiohttp.ClientSession() as session:
await asyncio.gather(*(try_delete(msg,session) for msg in msgs))
await author.send("done")
@telephone.command(brief="Generate a webhook")
@commands.check(util.server_mod_check)
async def init_webhook(self, ctx):
@ -223,7 +307,7 @@ When you want to end a call, use hangup.
if address == originating_address: return await ctx.send(embed=util.error_embed("A channel cannot dial itself. That means *you*, Gibson."))
recv_info = await self.get_addr_config(address)
if not recv_info: return await ctx.send(embed=util.error_embed("Destination address not found. Please check for typos and/or antimemes."))
current_call = await self.bot.database.execute_fetchone("SELECT * FROM calls WHERE from_id = ?", (originating_address,))
if current_call: return await ctx.send(embed=util.error_embed(f"A call is already open (to {current_call['to_id']}) from this channel. Currently, only one outgoing call is permitted at a time."))
@ -348,4 +432,4 @@ When you want to end a call, use hangup.
def setup(bot):
cog = Telephone(bot)
bot.add_cog(cog)
asyncio.create_task(cog.initial_load_webhooks())
asyncio.create_task(cog.initial_load_webhooks())

View File

@ -327,4 +327,9 @@ def random_id():
second_time -= SIMPLEFLAKE_EPOCH
millisecond_time = int(second_time * 1000)
randomness = random.getrandbits(SIMPLEFLAKE_RANDOM_LENGTH)
return (millisecond_time << SIMPLEFLAKE_TIMESTAMP_SHIFT) + randomness
return (millisecond_time << SIMPLEFLAKE_TIMESTAMP_SHIFT) + randomness
def chunks(source, length):
for i in range(0, len(source), length):
yield source[i : i+length]