async def cleanup_old_messages(self) -> None: """This will periodically iterate through old messages and prune them based on age to help keep data relatively easy to work through """ purge_time = await self.config.purge_time() if not purge_time: return purge = timedelta(seconds=purge_time) while True: total_pruned = 0 guilds_ignored = 0 to_purge = datetime.utcnow() - purge # Prune only the last 30 days worth of data for guild_id, starboards in self.starboards.items(): guild = self.bot.get_guild(guild_id) if not guild: guilds_ignored += 1 continue # log.debug(f"Cleaning starboard data for {guild.name} ({guild.id})") for name, starboard in starboards.items(): async with starboard.lock: to_rem = [] to_rem_index = [] try: async for message_ids, message in AsyncIter( starboard.messages.items(), steps=500): if message.new_message: if snowflake_time( message.new_message) < to_purge: to_rem.append(message_ids) index_key = f"{message.new_channel}-{message.new_message}" to_rem_index.append(index_key) else: if snowflake_time(message.original_message ) < to_purge: to_rem.append(message_ids) for m in to_rem: log.debug(f"Removing {m}") del starboard.messages[m] total_pruned += 1 for m in to_rem_index: del starboard.starboarded_messages[m] if len(to_rem) > 0: log.info( f"Starboard pruned {len(to_rem)} messages that are " f"{humanize_timedelta(timedelta=purge)} old from " f"{guild.name} ({guild.id})") except Exception: log.exception( "Error trying to clenaup old starboard messages." ) await self._save_starboards(guild) if total_pruned: log.info( f"Starboard has pruned {total_pruned} messages and ignored {guilds_ignored} guilds." ) # Sleep 1 day but also run on cog reload await asyncio.sleep(60 * 60 * 24)
async def copy_warn(user_id: int, warn: models.Warn): await add_dbmember_if_not_exist(user_id) warn.id = utils.time_snowflake( utils.snowflake_time(warn.id) + datetime.timedelta(milliseconds=1)) while await get_warn(warn.id): warn.id = utils.time_snowflake( utils.snowflake_time(warn.id) + datetime.timedelta(milliseconds=1)) warn.user = user_id await warn.create()
async def censor_detector(self): # reciever taks for someone gets censored while self.running: try: message = None message = await self.bot.wait_for("user_censored") # make sure our cog is still running so we don't handle it twice if not self.running: return # make sure anti-spam is enabled cfg = Configuration.get_var(message.guild.id, "ANTI_SPAM") if not cfg.get("ENABLED", False) or message.id in self.censor_processed: continue buckets = Configuration.get_var(message.guild.id, "ANTI_SPAM", "BUCKETS", []) count = 0 for b in buckets: t = b["TYPE"] if t == "censored": msg_time = int(snowflake_time(message.id).timestamp()) bucket = self.get_bucket(message.guild.id, f"censored:{count}", b, message.author.id) if bucket is not None and await bucket.check(message.author.id, msg_time, 1, f"{message.channel.id}-{message.id}"): count = await bucket.count(message.author.id, msg_time, expire=False) period = await bucket.size(message.author.id, msg_time, expire=False) self.bot.loop.create_task( self.violate(Violation("max_censored", message.guild, f"{Translator.translate('spam_max_censored', message)} ({count}/{period}s)", message.author, message.channel, await bucket.get(message.author.id, msg_time, expire=False), b, count))) except CancelledError: pass except Exception as e: await TheRealGearBot.handle_exception("censor detector", self.bot, e)
async def snowflake(self, ctx: Context, *snowflakes: Snowflake) -> None: """Get Discord snowflake creation time.""" if len(snowflakes) > 1 and await has_no_roles_check(ctx, *STAFF_ROLES): raise BadArgument( "Cannot process more than one snowflake in one invocation.") if not snowflakes: raise BadArgument("At least one snowflake must be provided.") embed = Embed(colour=Colour.blue()) embed.set_author( name= f"Snowflake{'s'[:len(snowflakes)^1]}", # Deals with pluralisation icon_url= "https://github.com/twitter/twemoji/blob/master/assets/72x72/2744.png?raw=true" ) lines = [] for snowflake in snowflakes: created_at = snowflake_time(snowflake) lines.append( f"**{snowflake}**\nCreated at {created_at} ({time_since(created_at, max_units=3)})." ) await LinePaginator.paginate(lines, ctx=ctx, embed=embed, max_lines=5, max_size=1000)
async def _before_first_ping(self) -> None: """ Sleep until `REMINDER_MESSAGE` should be sent again. If latest reminder is not cached, exit instantly. Otherwise, wait wait until the configured `REMINDER_FREQUENCY` has passed. """ last_reminder: t.Optional[int] = await self.task_cache.get( "last_reminder") if last_reminder is None: log.trace( "Latest verification reminder message not cached, task will not wait" ) return # Convert cached message id into a timestamp time_since = datetime.utcnow() - snowflake_time(last_reminder) log.trace(f"Time since latest verification reminder: {time_since}") to_sleep = timedelta( hours=constants.Verification.reminder_frequency) - time_since log.trace(f"Time to sleep until next ping: {to_sleep}") # Delta can be negative if `REMINDER_FREQUENCY` has already passed secs = max(to_sleep.total_seconds(), 0) await asyncio.sleep(secs)
async def send_exception(client: discord.Client, exception: Exception, source_name: str, mention_role: Optional[int] = 820974562770550816, pin: bool = True, timestamp: bool = True): """ sends the exception into a channel if `DATA.debug` ist disabled """ if not DATA.debug: super_log: discord.TextChannel = client.get_channel(DATA.IDs.Channels.Super_Log) file = discord.File(BytesIO(format_exc().encode()), "exception.log") embed: discord.Embed = discord.Embed(title=source_name, description=f"{exception.__class__.__name__}: {exception.__str__()}\n", color=discord.Color.magenta()) message: discord.Message = await super_log.send(embed=embed, file=file) if timestamp: from discord.utils import snowflake_time embed.add_field(name="datetime.datetime", value=snowflake_time(message.id).__str__()) await message.edit(embed=embed) if pin: await message.pin() if mention_role: await super_log.send(f"<@&{mention_role}>") else: raise exception
async def userinfobyid(self, ctx, uid: str): """Shows users's information""" server = ctx.message.server user = server.get_member(uid) user_created = snowflake_time(uid).strftime("%d %b %Y %H:%M") if user is not None: desc = "Is a member of this server" col = user.color name = "{0.name} #{0.discriminator}".format(user) if user.nick is not None: name += " AKA: {0.nick}".format(user) else: user = await self.bot.get_user_info(uid) if user is None: return await self.bot.say("No such user") col = discord.Color.purple() desc = "Not a member of this server" name = "{0.name} #{0.discriminator}".format(user) data = discord.Embed(description=desc, colour=col) if user.avatar_url: data.set_author(name=name, url=user.avatar_url) data.set_thumbnail(url=user.avatar_url) else: data.set_author(name=name) try: await self.bot.say(embed=data) except Exception: pass
async def convert(self, ctx: Context, arg: str) -> int: """ Ensure `arg` matches the ID pattern and its timestamp is in range. Return `arg` as an int if it's a valid snowflake. """ error = f"Invalid snowflake {arg!r}" if not self._get_id_match(arg): raise BadArgument(error) snowflake = int(arg) try: time = snowflake_time(snowflake) except (OverflowError, OSError) as e: # Not sure if this can ever even happen, but let's be safe. raise BadArgument(f"{error}: {e}") if time < DISCORD_EPOCH_DT: raise BadArgument(f"{error}: timestamp is before the Discord epoch.") elif (datetime.utcnow() - time).days < -1: raise BadArgument(f"{error}: timestamp is too far into the future.") return snowflake
async def on_reaction_add(self, reaction, user): if isinstance(reaction.message.channel, discord.DMChannel): return try: emoji = reaction.emoji.id db.update_emoji( { "e_id": emoji, "g_id": reaction.message.guild.id, "name": reaction.emoji.name, "animated": reaction.emoji.animated, "created_time": utils.snowflake_time(reaction.emoji.id), } ) except AttributeError: emoji = reaction.emoji db.update_emoji( { "e_id": emoji, "g_id": reaction.message.guild.id, "name": emoji, "animated": False, "created_time": END_TIME, } ) db.update_reaction( data={ "m_id": reaction.message.id, "e_id": emoji, "count": reaction.count, } )
async def cleaner(self): now = datetime.datetime.utcnow() for errorlog in self.errorlog.iterdir(): if not errorlog.name.startswith('.') and errorlog.suffix == '.log': messageid = base64.urlsafe_b64decode(errorlog.stem.encode()) time = snowflake_time(int.from_bytes(messageid, 'big')) if (now - time).total_seconds() > self.maxage: errorlog.unlink()
async def snowflake(self, ctx: Context, arg: int): if arg < 0: raise CommandError(t.invalid_snowflake) try: await reply(ctx, format_dt(snowflake_time(arg), style="F")) except (OverflowError, ValueError, OSError): raise CommandError(t.invalid_snowflake)
def to_json(self): return { "id": str(self.mid), "channel": str(self.channel), "member": self.hid, "system": self.system_hid, "message_sender": str(self.sender), "timestamp": snowflake_time(self.mid).isoformat() }
def test_snowflake_conversion(): from discord import utils now = datetime.datetime.utcnow() snowflake = utils.time_snowflake(now) time = utils.snowflake_time(snowflake) # Accept a millisecond or less of error assert abs((time - now).total_seconds()) <= 0.001
def is_valid_timestamp(b64_content: str) -> bool: b64_content += '=' * (-len(b64_content) % 4) try: content = base64.urlsafe_b64decode(b64_content) snowflake = struct.unpack('i', content)[0] except (binascii.Error, struct.error): return False return snowflake_time(snowflake + TOKEN_EPOCH) < DISCORD_EPOCH_TIMESTAMP
def test_snowflake_time(snowflake: int, time_tuple: typing.Tuple[int, int, int, int, int, int]): dt = utils.snowflake_time(snowflake) assert (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) == time_tuple assert utils.time_snowflake( dt, high=False) <= snowflake <= utils.time_snowflake(dt, high=True)
async def snowflake(self, ctx: Context, snowflake: Snowflake) -> None: """Get Discord snowflake creation time.""" created_at = snowflake_time(snowflake) embed = Embed( description=f"**Created at {created_at}** ({time_since(created_at, max_units=3)}).", colour=Colour.blue() ) embed.set_author( name=f"Snowflake: {snowflake}", icon_url="https://github.com/twitter/twemoji/blob/master/assets/72x72/2744.png?raw=true" ) await ctx.send(embed=embed)
def home(): if request.method == "POST": try: snowflake_id = request.form["id"] tz = pytz.timezone("America/Los_Angeles") tz2 = tz.localize(datetime.datetime.now()) snow_pst = snowflake_time(int(snowflake_id)).astimezone(tz) created_at = f'{datetime.datetime.strftime(snow_pst, "%A, %B %-d, %Y, %-I:%M:%S %p")} {tz2.tzname()}' timestamp = round(time.mktime(snowflake_time(int(snowflake_id)).timetuple())) iso = snowflake_time(int(snowflake_id)) return redirect(f"https://www.snowflake-time.tk/?id={request.form['id']}") except: return redirect("https://www.snowflake-time.tk/") if request.args.get("id"): try: snowflake_id = request.args.get("id") tz = pytz.timezone("America/Los_Angeles") tz2 = tz.localize(datetime.datetime.now()) snow_pst = snowflake_time(int(snowflake_id)).astimezone(tz) created_at = f'{datetime.datetime.strftime(snow_pst, "%A, %B %-d, %Y, %-I:%M:%S %p")} {tz2.tzname()}' timestamp = round(time.mktime(snowflake_time(int(snowflake_id)).timetuple())) iso = snowflake_time(int(snowflake_id)) return render_template("index.html", created_at=created_at, timestamp=timestamp, iso=iso, share_url=f"https://www.snowflake-time.tk/?id={snowflake_id}") except: return redirect("https://www.snowflake-time.tk/") return render_template("index.html")
async def task(self): try: log.spam("Running delete task...") for guild in self.bot.guilds: # get guild data and determine if this server is using auto delete guild_data = await self.bot.get_guild_data(guild.id) if guild_data: auto_del_data = guild_data.auto_delete_data if not auto_del_data: continue for channel_data in auto_del_data: channel: discord.TextChannel = self.bot.get_channel( int(channel_data["channel_id"])) if channel: messages_to_delete = {} async for message in channel.history( limit=None, after=datetime.utcnow() - timedelta(days=14)): message_age = datetime.utcnow( ) - snowflake_time(message.id) # message is younger than 14 days, but older than specified time if message_age.total_seconds() >= int( channel_data["delete_after"]) * 60: messages_to_delete[message.id] = message if len(messages_to_delete) >= 200: # to avoid api spamming, i only want to delete in batches of 200 at most break if len(messages_to_delete) != 0: messages_to_delete = list( messages_to_delete.values()) # bulk delete can only take 100 messages at a time, # so we chunk the deletions into batches of 100 or less message_chunk = [] for i, msg in enumerate(messages_to_delete): message_chunk.append(msg) if len(message_chunk) == 100 or i == len( messages_to_delete) - 1: await channel.delete_messages( message_chunk) message_chunk = [] log.spam( f"Deleted {len(messages_to_delete)} messages" ) await asyncio.sleep(0) except Exception as e: log.error("".join( traceback.format_exception(type(e), e, e.__traceback__)))
def is_valid_timestamp(b64_content: str) -> bool: """ Check potential token to see if it contains a valid timestamp. See: https://discordapp.com/developers/docs/reference#snowflakes """ b64_content += '=' * (-len(b64_content) % 4) try: content = base64.urlsafe_b64decode(b64_content) snowflake = struct.unpack('i', content)[0] except (binascii.Error, struct.error): return False return snowflake_time(snowflake + TOKEN_EPOCH) < DISCORD_EPOCH_TIMESTAMP
async def snowflake(self, ctx: Context, *snowflakes: Snowflake) -> None: """Get Discord snowflake creation time.""" if len(snowflakes) > 1 and await has_no_roles_check(ctx, *STAFF_ROLES): raise BadArgument("Cannot process more than one snowflake in one invocation.") for snowflake in snowflakes: created_at = snowflake_time(snowflake) embed = Embed( description=f"**Created at {created_at}** ({time_since(created_at, max_units=3)}).", colour=Colour.blue() ) embed.set_author( name=f"Snowflake: {snowflake}", icon_url="https://github.com/twitter/twemoji/blob/master/assets/72x72/2744.png?raw=true" ) await ctx.send(embed=embed)
def should_remove(self, seconds: int) -> bool: """ Returns True if we should end the event Returns False if the event should stay open """ now = datetime.now(timezone.utc).timestamp() if self.message is None: # If we don't even have a message linked to this event delete it # although in practice this should never happen return True if self.start: future = (self.start + timedelta(seconds=seconds)).timestamp() log.debug(f"{humanize_timedelta(seconds = future-now)}") return now > future else: future = ( snowflake_time(self.message).replace(tzinfo=timezone.utc) + timedelta(seconds=seconds)).timestamp() log.debug(f"{humanize_timedelta(seconds = future-now)}") return now > future
def remaining(self, seconds: int) -> str: """ Returns the time remaining on an event """ now = datetime.now(timezone.utc).timestamp() if self.message is None: # If we don't even have a message linked to this event delete it # although in practice this should never happen return _("0 seconds") if self.start: future = (self.start + timedelta(seconds=seconds)).timestamp() diff = future - now log.debug(f"Set time {future=} {now=} {diff=}") return humanize_timedelta(seconds=future - now) else: future = ( snowflake_time(self.message).replace(tzinfo=timezone.utc) + timedelta(seconds=seconds)).timestamp() diff = future - now log.debug(f"Message Time {future=} {now=} {diff=}") return humanize_timedelta(seconds=future - now)
async def userlogs(self, ctx: Context, user: Optional[Union[User, int]] = None): """ show moderation log of a user """ user, user_id, arg_passed = await self.get_stats_user(ctx, user) await update_join_date(self.bot.guilds[0], user_id) out: List[Tuple[datetime, str]] = [(snowflake_time(user_id), translations.ulog_created)] for join in await db_thread(db.all, Join, member=user_id): out.append((join.timestamp, translations.ulog_joined)) for leave in await db_thread(db.all, Leave, member=user_id): out.append((leave.timestamp, translations.ulog_left)) for username_update in await db_thread(db.all, UsernameUpdate, member=user_id): if not username_update.nick: msg = translations.f_ulog_username_updated( username_update.member_name, username_update.new_name) elif username_update.member_name is None: msg = translations.f_ulog_nick_set(username_update.new_name) elif username_update.new_name is None: msg = translations.f_ulog_nick_cleared( username_update.member_name) else: msg = translations.f_ulog_nick_updated( username_update.member_name, username_update.new_name) out.append((username_update.timestamp, msg)) for report in await db_thread(db.all, Report, member=user_id): out.append((report.timestamp, translations.f_ulog_reported(f"<@{report.reporter}>", report.reason))) for warn in await db_thread(db.all, Warn, member=user_id): out.append((warn.timestamp, translations.f_ulog_warned(f"<@{warn.mod}>", warn.reason))) for mute in await db_thread(db.all, Mute, member=user_id): text = [translations.ulog_muted, translations.ulog_muted_inf ][mute.days == -1][mute.is_upgrade].format if mute.days == -1: out.append((mute.timestamp, text(f"<@{mute.mod}>", mute.reason))) else: out.append((mute.timestamp, text(f"<@{mute.mod}>", mute.days, mute.reason))) if not mute.active and not mute.upgraded: if mute.unmute_mod is None: out.append((mute.deactivation_timestamp, translations.ulog_unmuted_expired)) else: out.append(( mute.deactivation_timestamp, translations.f_ulog_unmuted(f"<@{mute.unmute_mod}>", mute.unmute_reason), )) for kick in await db_thread(db.all, Kick, member=user_id): if kick.mod is not None: out.append((kick.timestamp, translations.f_ulog_kicked(f"<@{kick.mod}>", kick.reason))) else: out.append((kick.timestamp, translations.ulog_autokicked)) for ban in await db_thread(db.all, Ban, member=user_id): text = [translations.ulog_banned, translations.ulog_banned_inf ][ban.days == -1][ban.is_upgrade].format if ban.days == -1: out.append((ban.timestamp, text(f"<@{ban.mod}>", ban.reason))) else: out.append( (ban.timestamp, text(f"<@{ban.mod}>", ban.days, ban.reason))) if not ban.active and not ban.upgraded: if ban.unban_mod is None: out.append((ban.deactivation_timestamp, translations.ulog_unbanned_expired)) else: out.append(( ban.deactivation_timestamp, translations.f_ulog_unbanned(f"<@{ban.unban_mod}>", ban.unban_reason), )) for log in await db_thread(db.all, InviteLog, applicant=user_id): # type: InviteLog if log.approved: out.append((log.timestamp, translations.f_ulog_invite_approved( f"<@{log.mod}>", log.guild_name))) else: out.append((log.timestamp, translations.f_ulog_invite_removed( f"<@{log.mod}>", log.guild_name))) out.sort() embed = Embed(title=translations.userlogs, color=Colours.userlog) if isinstance(user, int): embed.set_author(name=str(user)) else: embed.set_author(name=f"{user} ({user_id})", icon_url=user.avatar_url) for row in out: name = row[0].strftime("%d.%m.%Y %H:%M:%S") value = row[1] embed.add_field(name=name, value=value, inline=False) embed.set_footer(text=translations.utc_note) if arg_passed: await send_long_embed(ctx, embed) else: try: await send_long_embed(ctx.author, embed) except (Forbidden, HTTPException): raise CommandError(translations.could_not_send_dm) await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
def get_time(self): return pendulum.parse(str(snowflake_time(self.msg_id))).utc()
async def whois(client, message, *args): if len(message.mentions) == 0: await message.channel.send(f"Usage: {PREFIX}whois <@user mention>") return user = message.mentions[0] embed = discord.Embed(title=f"{user.name}#{user.discriminator}", color=user.colour) tdelta = datetime.datetime.now() - user.joined_at embed.add_field(name="User ID", value=user.id) if user.nick: embed.add_field(name="Nickname", value=user.nick) if user.top_role: embed.add_field(name="Top Role", value=user.top_role) embed.add_field(name="Status", value=user.status) embed.add_field(name="Is Bot", value=user.bot) _perms = user.guild_permissions embed.add_field(name="Is Administrator", value=_perms.administrator) roles = user.roles[1:] if len(roles) > 0: role_str = ", ".join([i.name for i in roles]) else: role_str = "No roles set." embed.add_field(name="Roles", inline=False, value=role_str) embed.add_field( name="Account Created On", inline=False, value=snowflake_time(user.id).strftime("%A, %d %B, %Y. %I:%M:%S %p"), ) embed.add_field( name="In Server For", inline=False, value=f"{tdelta.days} days, {tdelta.seconds//3600} hours", ) perms_list = [ "kick_members", "ban_members", "manage_channels", "manage_guild", "add_reactions", "view_audit_log", "priority_speaker", "send_messages", "send_tts_messages", "manage_messages", "attach_files", "read_message_history", "mention_everyone", "embed_links", "external_emojis", "connect", "speak", "mute_members", "deafen_members", "move_members", "use_voice_activation", "change_nickname", "manage_nicknames", "manage_roles", "manage_webhooks", "manage_emojis", ] perms = [] for i in perms_list: if getattr(_perms, i): perms += [i.replace("_", " ").capitalize()] if perms == []: perms = ["No special permissions."] perms_str = ", ".join(perms) embed.add_field(name="Permissions", value=perms_str, inline=False) embed.set_thumbnail(url=user.avatar_url) await message.channel.send(embed=embed)
def created_at(self) -> int: return snowflake_time(self.id).timestamp()
def created_at(self): """:class:`datetime.datetime`: Returns the webhook's creation time in UTC.""" return utils.snowflake_time(self.id)
async def __main__(client: Client, _event: int, *args: Union[Message, Member, VoiceState, User, Member, Role]): try: super_log: TextChannel = client.get_channel(DATA.IDs.Channels.Super_Log) attachments = None if _event == EVENT.on_message: datetime_edit = False if args[0].channel.id != super_log.id: embed: Embed = Embed(title=f"on_message | " f"<{args[0].jump_url}> |" + (f" {args[0].channel.category} |" f" {args[0].channel.mention}" if hasattr(args[0].channel, "category") else " DM"), description=args[0].content+(" | EMBED" if args[0].embeds else "")+(" | ATTACHMENT" if args[0].attachments else ""), color=Color.gold()) embed.set_author(name=args[0].author, url=args[0].author.avatar_url) embed.add_field(name="datetime.datetime", value=args[0].created_at) from io import BytesIO if args[0].attachments: attachments = [] for attachment in args[0].attachments: fp = BytesIO() await attachment.save(fp) attachments.append(File(fp, attachment.filename)) from json import dump if args[0].embeds: attachments = [] for i, attachment in enumerate(args[0].embeds): with open("_.log", "w") as fp: dump(attachment.to_dict(), fp, indent=2) with open("_.log", "rb") as fp: attachments.append(File(fp, f"EMBED-{i}.json")) else: return elif _event == EVENT.on_message_delete: datetime_edit = False embed: Embed = Embed(title=f"on_message_delete | " f"<{args[0].jump_url}> |" + (f" {args[0].channel.category} |" f" {args[0].channel.mention}" if hasattr(args[0].channel, "category") else " DM"), description=args[0].content+(" | EMBED" if args[0].embeds else "")+(" | ATTACHMENT" if args[0].attachments else ""), color=Color.gold()) embed.set_author(name=args[0].author, url=args[0].author.avatar_url) embed.add_field(name="datetime.datetime", value=args[0].created_at) from io import BytesIO if args[0].attachments: attachments = [] for attachment in args[0].attachments: fp = BytesIO() await attachment.save(fp) attachments.append(File(fp, attachment.filename)) from json import dump if args[0].embeds: attachments = [] for i, attachment in enumerate(args[0].embeds): with open("_.log", "w") as fp: dump(attachment.to_dict(), fp, indent=2) with open("_.log", "rb") as fp: attachments.append(File(fp, f"EMBED-{i}.json")) elif _event == EVENT.on_message_edit: datetime_edit = False if args[0].author.id != client.user.id: embed: Embed = Embed(title=f"on_message_edit | " f"<{args[0].jump_url}> |" + (f" {args[0].channel.category} |" f" {args[0].channel.mention}" if hasattr(args[0].channel, "category") else " DM"), color=Color.gold()) embed.set_author(name=args[0].author, url=args[0].author.avatar_url) embed.add_field(name=f"before ({args[0].created_at})", value=args[0].content+(" | EMBED" if args[0].embeds else "")+(" | ATTACHMENT" if args[0].attachments else "")) embed.add_field(name=f"after ({args[0].edited_at})", value=args[1].content+(" | EMBED" if args[0].embeds else "")+(" | ATTACHMENT" if args[0].attachments else "")) from io import BytesIO if args[0].attachments: attachments = [] for attachment in args[0].attachments: fp = BytesIO() await attachment.save(fp) attachments.append(File(fp, attachment.filename)) from json import dump if args[0].embeds: attachments = [] for i, attachment in enumerate(args[0].embeds): with open("_.log", "w") as fp: dump(attachment.to_dict(), fp, indent=2) with open("_.log", "rb") as fp: attachments.append(File(fp, f"EMBED-OLD-{i}.json")) if args[1].attachments: attachments = [] for attachment in args[1].attachments: fp = BytesIO() await attachment.save(fp) attachments.append(File(fp, attachment.filename)) if args[1].embeds: attachments = [] for i, attachment in enumerate(args[1].embeds): with open("_.log", "w") as fp: dump(attachment.to_dict(), fp, indent=2) with open("_.log", "rb") as fp: attachments.append(File(fp, f"EMBED-NEW-{i}.json")) else: return elif _event == EVENT.on_ready: datetime_edit = True embed: Embed = Embed(title=f"on_ready", color=Color.green()) elif _event == EVENT.on_voice_state_update: datetime_edit = True embed: Embed = Embed(title=f"on_voice_state_update", color=Color.greyple()) embed.set_author(name=args[0].__str__(), url=args[0].avatar_url) if args[1].channel is None: embed.description = f"joined {args[2].channel}" if args[2].channel is None: embed.description = f"leaved {args[1].channel}" if args[1].channel is not None and args[2].channel is not None and args[1].channel != args[2].channel: embed.add_field(name="moved", value=f"{args[1].channel} -> {args[2].channel}") if args[1].deaf != args[2].deaf: embed.add_field(name="server deafen", value=f"{args[1].deaf} -> {args[2].deaf}") if args[1].mute != args[2].mute: embed.add_field(name="server mute", value=f"{args[1].mute} -> {args[2].mute}") if args[1].self_deaf != args[2].self_deaf: embed.add_field(name="self self_deafen", value=f"{args[1].self_deaf} -> {args[2].self_deaf}") if args[1].self_mute != args[2].self_mute: embed.add_field(name="self mute", value=f"{args[1].self_mute} -> {args[2].self_mute}") if args[1].self_stream != args[2].self_stream: embed.add_field(name="self stream", value=f"{args[1].self_stream} -> {args[2].self_stream}") if args[1].self_video != args[2].self_video: embed.add_field(name="self video", value=f"{args[1].self_video} -> {args[2].self_video}") if args[1].afk != args[2].afk: embed.add_field(name="afk", value=f"{args[1].afk} -> {args[2].afk}") elif _event == EVENT.on_user_update: datetime_edit = True embed: Embed = Embed(title=f"on_user_update", color=Color.blurple()) embed.set_author(name=args[0].__str__(), url=args[0].avatar_url) if args[0].avatar != args[1].avatar: embed.add_field(name="avatar", value=f"[IMG]({args[0].avatar_url}) -> [IMG]({args[1].avatar_url})") if args[0].name != args[1].name: embed.add_field(name="name", value=f"{args[0].name} -> {args[1].name}") if args[0].discriminator != args[1].discriminator: embed.add_field(name="discriminator", value=f"{args[0].discriminator} -> {args[1].discriminator}") elif _event == EVENT.on_member_update: datetime_edit = True embed: Embed = Embed(title=f"on_member_update", color=Color.blue()) embed.set_author(name=args[0].__str__(), url=args[0].avatar_url) if args[0].nick != args[1].nick: embed.add_field(name="nickname", value=f"{args[0].nick} -> {args[1].nick}") if args[0].roles != args[1].roles: if len(args[0].roles) < len(args[1].roles): meta = list() for role in args[1].roles: if role not in args[0].roles: meta.append(f"**+** `{role.name}` `{role.id}`") embed.add_field(name="role", value="\n".join(meta)) if len(args[0].roles) > len(args[1].roles): meta = list() for role in args[0].roles: if role not in args[1].roles: meta.append(f"**-** `{role.name}` `{role.id}`") embed.add_field(name="role", value="\n".join(meta)) if len(args[0].roles) == len(args[1].roles): embed.add_field(name="role", value="***__PLEASE CONTACT A <@820974562770550816>!!!__***") if len(embed.fields) == 0: return else: datetime_edit = True embed = Embed() message: Message = await super_log.send(embed=embed, files=attachments) if datetime_edit: from discord.utils import snowflake_time embed.add_field(name="datetime.datetime", value=snowflake_time(message.id).__str__(), inline=False) await message.edit(embed=embed) except Exception as e: await send_exception(client=client, exception=e, source_name=__name__)
def created_at(self): """Returns the user's creation time in UTC. This is when the user's discord account was created.""" return snowflake_time(self.id)
async def background_loop(self): """ Check for age of the last message sent in the channel. If it's too old, consider the channel inactive and close the ticket. """ category = await self.get_forwarding_category() now = datetime.datetime.now() one_day_ago = now - datetime.timedelta(days=1) for ticket_channel in category.text_channels: last_message_id = ticket_channel.last_message_id if last_message_id: # We have the ID of the last message, there is no need to fetch the API, since we can just extract the # datetime from it. last_message_time = snowflake_time(last_message_id) else: # For some reason, we couldn't get the last message, so we'll have to go the expensive route. # In my testing, I didn't see it happen, but better safe than sorry. try: last_message = (await ticket_channel.history(limit=1 ).flatten())[0] except IndexError: # No messages at all. last_message_time = now else: last_message_time = last_message.created_at inactive_for = last_message_time - now inactive_for_str = format_timedelta(inactive_for, granularity='minute', locale='en', threshold=1.1) self.bot.logger.debug( f"[SUPPORT GARBAGE COLLECTOR] " f"#{ticket_channel.name} has been inactive for {inactive_for_str}." ) if last_message_time <= one_day_ago: self.bot.logger.debug(f"[SUPPORT GARBAGE COLLECTOR] " f"Deleting #{ticket_channel.name}...") # The last message was sent a day ago, or more. # It's time to close the channel. async with ticket_channel.typing(): user = await self.get_user(ticket_channel.name) db_user = await get_from_db(user, as_user=True) ticket = await db_user.get_or_create_support_ticket() ticket.close( await get_from_db(self.bot.user, as_user=True), "Automatic closing for inactivity.") await ticket.save() language = db_user.language _ = get_translate_function(self.bot, language) inactivity_embed = discord.Embed( color=discord.Color.orange(), title=_("DM Timed Out"), description= _("It seems like nothing has been said here for a long time, " "so I've gone ahead and closed your ticket, deleting its history. " "Thanks for using DuckHunt DM support. " "If you need anything else, feel free to open a new ticket by sending a message " "here."), ) inactivity_embed.add_field( name=_("Support server"), value= _("For all your questions, there is a support server. " "Click [here](https://duckhunt.me/support) to join." )) try: await user.send(embed=inactivity_embed) except: pass await self.clear_caches(ticket_channel) await ticket_channel.delete( reason=f"Automatic deletion for inactivity.")