g1mp/plugins/bomb.py
2025-02-14 19:17:00 -08:00

203 lines
7.5 KiB
Python

"""
IRC3 Bot Plugin: Periodic Messaging and Nickname Manipulation
This plugin for an IRC bot automates periodic messages, nickname changes,
and user listing within a channel. It supports starting and stopping these
tasks dynamically via bot commands.
Features:
- Sends a periodic empty message (spam prevention technique).
- Changes the bot's nickname periodically to mimic users.
- Lists users in the channel periodically in manageable chunks.
Author: Zodiac
"""
import asyncio
import irc3
import random
import textwrap
from irc3.plugins.command import command
@irc3.plugin
class PeriodicMessagePlugin:
"""A plugin to periodically send messages, change nicknames, and list users."""
def __init__(self, bot):
"""
Initialize the plugin with bot reference and default parameters.
Args:
bot (irc3.IrcBot): The IRC bot instance.
"""
self.bot = bot
self.channel = "" # The IRC channel the bot is operating in
self.periodic_message = ' \u2002 \u2003 ' * 500 # Empty message trick
self.tasks = [] # Stores running asyncio tasks
self.running = False # Flag to control task execution
self.original_nick = "" # Store the original bot nickname
# Sleep durations for various tasks (in seconds)
self.sleep_durations = {
'_send_periodic_message': 17,
'_change_nick_periodically': 50,
'_send_listusers_periodically': 60,
}
@irc3.event(irc3.rfc.JOIN)
def on_join(self, mask, channel, **kwargs):
"""
Handle the bot joining a channel.
Args:
mask (str): The user mask.
channel (str): The channel name.
kwargs (dict): Additional keyword arguments.
"""
self.channel = channel
if not self.running:
pass # Uncomment below if auto-starting periodic tasks
# self.start_periodic_tasks()
def start_periodic_tasks(self):
"""Start all periodic tasks asynchronously."""
self.running = True
self._cancel_tasks() # Ensure no duplicate tasks exist
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 completed tasks and restart if necessary.
Args:
task (asyncio.Task): The completed task.
"""
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() # Restart tasks if still running
async def _send_periodic_message(self):
"""Send an empty periodic message to the channel."""
try:
while self.running:
self.bot.privmsg(self.channel, self.periodic_message)
self.bot.log.info(f"Message sent to {self.channel}.")
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 the bot's nickname periodically to mimic a random user."""
try:
self.original_nick = self.bot.nick # Store original nickname
while self.running:
channel_key = self.channel.lower()
if channel_key in self.bot.channels:
users = list(self.bot.channels[channel_key])
if users:
random_user = random.choice(users)
new_nick = f"{random_user}_"
self.bot.send(f'NICK {new_nick}')
self.bot.nick = new_nick
self.bot.log.info(f"Nickname changed to: {new_nick}")
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 a list of users in the channel periodically."""
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)
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) # Short delay to avoid flooding
self.bot.log.info(f"User list sent to {self.channel}.")
await asyncio.sleep(self.sleep_durations['_send_listusers_periodically'])
except asyncio.CancelledError:
pass
except Exception as e:
self.bot.log.error(f"Error sending user list: {e}")
@command(permission='admin')
def stopannoy(self, mask, target, args):
"""
Stop all periodic tasks and revert nickname.
Usage:
%%stopannoy
"""
if mask.nick == self.bot.config.get('owner', ''):
self.running = False
self._cancel_tasks()
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."
return "Permission denied."
@command(permission='admin')
async def annoy(self, mask, target, args):
"""
Start periodic tasks via a command.
Usage:
%%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 formatted message.
Usage:
%%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
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, users_msg)
await asyncio.sleep(0.007) # Prevent flooding
else:
return f"Channel {self.channel} not found."