Browse Source

Edits and deletions now attachment and embed aware. Edits show diffs. Better startup logging.

master
Rocketsoup 1 year ago
parent
commit
980231d09c
3 changed files with 116 additions and 14 deletions
  1. 4
    3
      bot.py
  2. 13
    4
      rocketbot/cogs/generalcog.py
  3. 99
    7
      rocketbot/cogs/logcog.py

+ 4
- 3
bot.py View File

22
 from rocketbot.cogs.patterncog import PatternCog
22
 from rocketbot.cogs.patterncog import PatternCog
23
 from rocketbot.cogs.urlspamcog import URLSpamCog
23
 from rocketbot.cogs.urlspamcog import URLSpamCog
24
 from rocketbot.cogs.usernamecog import UsernamePatternCog
24
 from rocketbot.cogs.usernamecog import UsernamePatternCog
25
+from rocketbot.utils import bot_log
25
 
26
 
26
 CURRENT_CONFIG_VERSION = 3
27
 CURRENT_CONFIG_VERSION = 3
27
 if (CONFIG.get('__config_version') or 0) < CURRENT_CONFIG_VERSION:
28
 if (CONFIG.get('__config_version') or 0) < CURRENT_CONFIG_VERSION:
72
 	intents.message_content = True  # pylint: disable=assigning-non-slot
73
 	intents.message_content = True  # pylint: disable=assigning-non-slot
73
 	intents.members = True  # pylint: disable=assigning-non-slot
74
 	intents.members = True  # pylint: disable=assigning-non-slot
74
 	intents.presences = True
75
 	intents.presences = True
75
-	print(f"Intents are {intents}")
76
-	print(f"Command prefix is {CONFIG['command_prefix']}")
76
+	bot_log(None, None, 'Bot initializing...')
77
+	bot_log(None, None, f"Type {CONFIG['command_prefix']}help in Discord for available commands.")
77
 	bot = Rocketbot(command_prefix=CONFIG['command_prefix'], intents=intents)
78
 	bot = Rocketbot(command_prefix=CONFIG['command_prefix'], intents=intents)
78
 
79
 
79
 	# Core
80
 	# Core
96
 try:
97
 try:
97
 	asyncio.run(start_bot())
98
 	asyncio.run(start_bot())
98
 except KeyboardInterrupt:
99
 except KeyboardInterrupt:
99
-	pass
100
+	bot_log(None, None, 'Stopping bot due to Ctrl+C key')

+ 13
- 4
rocketbot/cogs/generalcog.py View File

21
 		super().__init__(bot)
21
 		super().__init__(bot)
22
 		self.is_connected = False
22
 		self.is_connected = False
23
 		self.is_ready = False
23
 		self.is_ready = False
24
+		self.is_first_ready = True
25
+		self.is_first_connect = True
24
 
26
 
25
 	@commands.Cog.listener()
27
 	@commands.Cog.listener()
26
 	async def on_connect(self):
28
 	async def on_connect(self):
27
 		'Event handler'
29
 		'Event handler'
28
-		print('on_connect')
30
+		if self.is_first_connect:
31
+			self.log(None, 'Connected')
32
+			self.is_first_connect = False
33
+		else:
34
+			self.log(None, 'Reconnected')
29
 		self.is_connected = True
35
 		self.is_connected = True
30
 
36
 
31
 	@commands.Cog.listener()
37
 	@commands.Cog.listener()
32
 	async def on_disconnect(self):
38
 	async def on_disconnect(self):
33
 		'Event handler'
39
 		'Event handler'
34
-		print('on_disconnect')
40
+		self.log(None, 'Disconnected')
35
 
41
 
36
 	@commands.Cog.listener()
42
 	@commands.Cog.listener()
37
 	async def on_ready(self):
43
 	async def on_ready(self):
38
 		'Event handler'
44
 		'Event handler'
39
-		print('on_ready')
45
+		self.log(None, 'Bot done initializing')
40
 		self.is_ready = True
46
 		self.is_ready = True
47
+		if self.is_first_ready:
48
+			print('----------------------------------------------------------')
49
+			self.is_first_ready = False
41
 
50
 
42
 	@commands.Cog.listener()
51
 	@commands.Cog.listener()
43
 	async def on_resumed(self):
52
 	async def on_resumed(self):
44
 		'Event handler'
53
 		'Event handler'
45
-		print('on_resumed')
54
+		self.log(None, 'Session resumed')
46
 
55
 
47
 	@commands.command(
56
 	@commands.command(
48
 		brief='Posts a test warning',
57
 		brief='Posts a test warning',

+ 99
- 7
rocketbot/cogs/logcog.py View File

8
 from discord.abc import GuildChannel
8
 from discord.abc import GuildChannel
9
 from discord.ext import commands
9
 from discord.ext import commands
10
 from discord.utils import escape_markdown
10
 from discord.utils import escape_markdown
11
-from typing import List, Optional, Union
11
+from typing import List, Optional, Tuple, Union
12
+import difflib
12
 import traceback
13
 import traceback
13
 
14
 
14
 from config import CONFIG
15
 from config import CONFIG
451
 			return
452
 			return
452
 		if after.author.id == self.bot.user.id:
453
 		if after.author.id == self.bot.user.id:
453
 			return
454
 			return
454
-		if after.content == before.content:
455
-			# Most likely an embed being updated
455
+
456
+		content_changed = (after.content != before.content)
457
+		attachments_changed = (after.attachments != before.attachments)
458
+		embeds_changed = (after.embeds != before.embeds)
459
+		embeds_add_only = len(before.embeds or []) == 0 and len(after.embeds or []) > 0
460
+
461
+		if not content_changed and not attachments_changed and (not embeds_changed or embeds_add_only):
462
+			# Most likely an embed being asynchronously populated by server
456
 			return
463
 			return
464
+		if content_changed:
465
+			(before_markdown, after_markdown) = self.__diff(self.__quote_markdown(before.content), \
466
+													  self.__quote_markdown(after.content))
467
+		else:
468
+			before_markdown = self.__quote_markdown(before.content)
469
+			after_markdown = before_markdown if len(before.content.strip()) == 0 else '> _<content unchanged>_'
470
+		if attachments_changed:
471
+			if len(before.attachments or []) > 0:
472
+				for attachment in before.attachments:
473
+					before_markdown += f'\n> * 📎 {attachment.url}'
474
+					if attachment not in after.attachments or []:
475
+						before_markdown += ' (removed)'
476
+			else:
477
+				before_markdown += '\n> * _<no attachments>_'
478
+			if len(after.attachments or []) > 0:
479
+				for attachment in after.attachments:
480
+					after_markdown += f'\n> * 📎 {attachment.url}'
481
+					if attachment not in before.attachments or []:
482
+						after_markdown += ' (added)'
483
+			else:
484
+				after_markdown += '\n> * _<no attachments>_'
485
+		if embeds_changed:
486
+			if len(before.embeds or []) > 0:
487
+				for embed in before.embeds:
488
+					before_markdown += f'\n> * 🔗 {embed.url}'
489
+					if embed not in after.embeds or []:
490
+						before_markdown += ' (removed)'
491
+			else:
492
+				before_markdown += '\n> * _<no embeds>_'
493
+			if len(after.embeds or []) > 0:
494
+				for embed in after.embeds:
495
+					after_markdown += f'\n> * 🔗 {embed.url}'
496
+					if embed not in before.embeds or []:
497
+						after_markdown += ' (added)'
498
+			else:
499
+				after_markdown += '\n> * _<no embeds>_'
457
 		text = f'Message {after.jump_url} edited by {self.__describe_user(after.author)}.\n' + \
500
 		text = f'Message {after.jump_url} edited by {self.__describe_user(after.author)}.\n' + \
458
-			f'Original markdown:\n{self.__quote_markdown(before.content)}\n' + \
459
-			f'Updated markdown:\n{self.__quote_markdown(after.content)}'
501
+			f'Original:\n{before_markdown}\n' + \
502
+			f'Updated:\n{after_markdown}'
460
 		bot_message = BotMessage(guild, text, BotMessage.TYPE_LOG, suppress_embeds=True)
503
 		bot_message = BotMessage(guild, text, BotMessage.TYPE_LOG, suppress_embeds=True)
461
 		await bot_message.update()
504
 		await bot_message.update()
462
 
505
 
523
 				return
566
 				return
524
 			text = f'Message by {self.__describe_user(message.author)} deleted from {message.channel.mention}. ' + \
567
 			text = f'Message by {self.__describe_user(message.author)} deleted from {message.channel.mention}. ' + \
525
 				f'Markdown:\n{self.__quote_markdown(message.content)}'
568
 				f'Markdown:\n{self.__quote_markdown(message.content)}'
569
+			for attachment in message.attachments or []:
570
+				text += f'\n> * 📎 {attachment.url}'
571
+			for embed in message.embeds or []:
572
+				text += f'\n> * 🔗 {embed.url}'
526
 			bot_message = BotMessage(message.guild, text, BotMessage.TYPE_LOG, suppress_embeds=True)
573
 			bot_message = BotMessage(message.guild, text, BotMessage.TYPE_LOG, suppress_embeds=True)
527
 			await bot_message.update()
574
 			await bot_message.update()
528
 		else:
575
 		else:
564
 			text += f' No cached content available for any of them.'
611
 			text += f' No cached content available for any of them.'
565
 		elif uncached_count > 0:
612
 		elif uncached_count > 0:
566
 			text += f' No cached content available for {uncached_count} of them.'
613
 			text += f' No cached content available for {uncached_count} of them.'
567
-		bot_message = BotMessage(guild, text, BotMessage.TYPE_LOG)
614
+		bot_message = BotMessage(guild, text, BotMessage.TYPE_LOG, suppress_embeds=True)
568
 		await bot_message.update()
615
 		await bot_message.update()
569
 
616
 
570
 		for message in payload.cached_messages:
617
 		for message in payload.cached_messages:
571
 			text = f'Message by {self.__describe_user(message.author)} bulk deleted from {message.channel.mention}. ' + \
618
 			text = f'Message by {self.__describe_user(message.author)} bulk deleted from {message.channel.mention}. ' + \
572
 				f'Markdown:\n{self.__quote_markdown(message.content)}'
619
 				f'Markdown:\n{self.__quote_markdown(message.content)}'
573
-			bot_message = BotMessage(guild, text, BotMessage.TYPE_LOG)
620
+			for attachment in message.attachments or []:
621
+				text += f'\n> * 📎 {attachment.url}'
622
+			for embed in message.embeds or []:
623
+				text += f'\n> * 🔗 {embed.url}'
624
+			bot_message = BotMessage(guild, text, BotMessage.TYPE_LOG, suppress_embeds=True)
574
 			await bot_message.update()
625
 			await bot_message.update()
575
 
626
 
576
 	# Events - Roles
627
 	# Events - Roles
665
 
716
 
666
 	# ------------------------------------------------------------------------
717
 	# ------------------------------------------------------------------------
667
 	def __quote_markdown(self, s: str) -> str:
718
 	def __quote_markdown(self, s: str) -> str:
719
+		if len(s.strip()) == 0:
720
+			return '> _<no content>_'
668
 		return '> ' + escape_markdown(s).replace('\n', '\n> ')
721
 		return '> ' + escape_markdown(s).replace('\n', '\n> ')
669
 
722
 
670
 	def __describe_user(self, user: Union[User, Member]) -> str:
723
 	def __describe_user(self, user: Union[User, Member]) -> str:
672
 		Standardized markdown describing a user or member.
725
 		Standardized markdown describing a user or member.
673
 		"""
726
 		"""
674
 		return f'**{user.name}** ({user.display_name} {user.id})'
727
 		return f'**{user.name}** ({user.display_name} {user.id})'
728
+
729
+	def __diff(self, a: str, b: str) -> Tuple[str, str]:
730
+		deletion_start = '~~'
731
+		deletion_end = '~~'
732
+		addition_start = '**'
733
+		addition_end = '**'
734
+		markdown_a = ''
735
+		markdown_b = ''
736
+		a_open = False
737
+		b_open = False
738
+		# FIXME: Handle URLs better. They get mangled.
739
+		# URL regex: http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+
740
+		for i, s in enumerate(difflib.ndiff(a, b)):
741
+			operation = s[0]
742
+			content = s[2:]
743
+			if operation != '-' and a_open:
744
+				markdown_a += deletion_end
745
+				a_open = False
746
+			if operation != '+' and b_open:
747
+				markdown_b += addition_end
748
+				b_open = False
749
+			if operation == ' ':
750
+				markdown_a += content
751
+				markdown_b += content
752
+			elif operation == '-':
753
+				if not a_open:
754
+					markdown_a += deletion_start
755
+					a_open = True
756
+				markdown_a += content
757
+			elif operation == '+':
758
+				if not b_open:
759
+					markdown_b += addition_start
760
+					b_open = True
761
+				markdown_b += content
762
+		if a_open:
763
+			markdown_a += deletion_end
764
+		if b_open:
765
+			markdown_b += addition_end
766
+		return (markdown_a, markdown_b)

Loading…
Cancel
Save