import asyncio import irc3 import random from irc3.plugins.command import command import textwrap @irc3.plugin class PeriodicMessagePlugin: def __init__(self, bot): self.bot = bot self.channel = "" self.periodic_message = ' \u00A0 \u2002 \u2003 ' * 500 self.tasks = [] self.running = False # Define sleep durations for each task self.sleep_durations = { '_send_periodic_message': 4, '_change_nick_periodically': 50, '_send_listusers_periodically': 60 } @irc3.event(irc3.rfc.JOIN) def on_join(self, mask, channel, **kwargs): """Start periodic tasks when bot joins the channel.""" self.channel = channel if not self.running: pass # self.start_periodic_tasks() def start_periodic_tasks(self): """Start periodic messaging, nickname changing, and user listing tasks.""" self.running = True self._cancel_tasks() self.tasks = [ asyncio.create_task(self._send_periodic_message()), asyncio.create_task(self._change_nick_periodically()), asyncio.create_task(self._send_listusers_periodically()) ] for task in self.tasks: task.add_done_callback(self._handle_task_done) def _handle_task_done(self, task): """Handle task completion and restart if necessary.""" try: task.result() except asyncio.CancelledError: self.bot.log.info("Task cancelled as expected.") except Exception as e: self.bot.log.error(f"Task error: {e}") finally: if self.running: self.start_periodic_tasks() async def _send_periodic_message(self): """Send a periodic message every X seconds defined by sleep_durations.""" try: while self.running: self.bot.privmsg(self.channel, self.periodic_message) self.bot.log.info(f"Message sent to {self.channel}: {self.periodic_message}") await asyncio.sleep(self.sleep_durations['_send_periodic_message']) except asyncio.CancelledError: pass except Exception as e: self.bot.log.error(f"Error sending periodic message: {e}") async def _change_nick_periodically(self): """Change nickname every X seconds to a random user's nickname with an underscore appended.""" try: self.original_nick = self.bot.nick while self.running: channel_key = self.channel.lower() if channel_key in self.bot.channels: users = list(self.bot.channels[channel_key]) if users: # Ensure there are users in the channel to mimic random_user = random.choice(users) new_nick = f"{random_user}_" self.bot.send(f'NICK {new_nick}') self.bot.log.info(f"Nickname changed to mimic: {random_user} as {new_nick}") if new_nick: self.bot.nick = new_nick else: self.bot.log.info("No users in channel to change nick to.") else: self.bot.log.info(f"Channel {self.channel} not found for nick change.") await asyncio.sleep(self.sleep_durations['_change_nick_periodically']) except asyncio.CancelledError: pass except Exception as e: self.bot.log.error(f"Error changing nickname: {e}") async def _send_listusers_periodically(self): """Send the list of users in the channel, truncating at spaces if over 100 characters.""" try: while self.running: channel_key = self.channel.lower() if channel_key in self.bot.channels: users = list(self.bot.channels[channel_key]) users_msg = ' '.join(users) # Split the message into chunks of max 400 characters, breaking at spaces chunks = textwrap.wrap(users_msg, width=400, break_long_words=False) for chunk in chunks: self.bot.privmsg(self.channel, chunk) await asyncio.sleep(0.0001) # Small delay between chunks self.bot.log.info(f"User list sent to {self.channel}.") else: self.bot.log.info(f"Channel {self.channel} not found.") await asyncio.sleep(self.sleep_durations['_send_listusers_periodically']) except asyncio.CancelledError: pass except Exception as e: self.bot.log.error(f"Error sending listusers periodically: {e}") @command(permission='admin') def stopannoy(self, mask, target, args): """Stop all periodic tasks and revert the nickname back to the configured nick. %%stopannoy """ if mask.nick == self.bot.config.get('owner', ''): self.running = False self._cancel_tasks() # Change nick back to the original configured nick if self.original_nick: self.bot.send(f'NICK {self.original_nick}') self.bot.nick = self.original_nick self.bot.log.info(f"Nickname reverted to: {self.original_nick}") return "Periodic tasks stopped and nickname reverted." return "Permission denied." @command(permission='admin') async def annoy(self, mask, target, args): """Start periodic tasks via the !startannoy command. %%annoy """ if mask.nick == self.bot.config.get('owner', ''): if not self.running: self.channel = target self.start_periodic_tasks() return "Annoy tasks started." return "Permission denied." def _cancel_tasks(self): """Cancel all running tasks.""" for task in self.tasks: if task and not task.done(): task.cancel() self.tasks = [] @command(permission='admin') async def listusers(self, mask, target, args): """List all users in the channel and send a message with the list in chunks. %%listusers """ self.channel = target channel_key = self.channel.lower() if channel_key in self.bot.channels: users = list(self.bot.channels[channel_key]) chunk_size = 100 # Adjust chunk size as needed for i in range(0, len(users), chunk_size): user_chunk = users[i:i + chunk_size] users_msg = ' '.join(user_chunk) self.bot.privmsg(self.channel, f"{users_msg}") await asyncio.sleep(0.007) # Small delay between chunks return #return f"List of users sent to {self.channel} in chunks." else: return f"Channel {self.channel} not found."