g1mp/plugins/random_msg.py
2025-02-12 22:35:15 -08:00

147 lines
4.9 KiB
Python

# -*- coding: utf-8 -*-
"""
IRC3 Bot Plugin: Mass Messaging
This plugin for an IRC bot enables mass messaging to all users in a channel using an asynchronous queue system.
It supports sending messages to multiple users with a small delay between messages to prevent flooding.
Features:
- Sends a message to all users in a channel.
- Uses an asynchronous queue system for efficient message processing.
- Handles IRC mode prefixes in nicknames.
- Provides logging for sent and failed messages.
Usage:
======
To use this module, load it as a plugin in your IRC bot configuration.
Example:
@command
def msgall(self, mask, target, args):
%%msgall <message>...
This command can only be used in a channel.
Author: Zodiac
Date: 2025-02-13 06:15:38 (UTC)
"""
import irc3
from irc3.plugins.command import command
from irc3.compat import Queue
import asyncio
def strip_nick_prefix(nick):
"""Remove IRC mode prefixes from a nickname."""
return nick.lstrip('@+%&~!') if nick else ''
@irc3.plugin
class MassMessagePlugin:
"""Mass messaging plugin using async queue system."""
def __init__(self, bot):
"""
Initialize the plugin with bot reference.
Args:
bot (irc3.IrcBot): The IRC bot instance.
"""
self.bot = bot
self.delay = 0.0001 # Delay between messages in seconds
self.queue = Queue() # Using irc3's compatibility Queue
self.count = 0 # Counter for successfully sent messages
async def _worker(self, message, total):
"""
Worker task to process messages from the queue.
Args:
message (str): The message to send.
total (int): The total number of recipients.
"""
while True:
try:
nick = await self.queue.get()
try:
if nick == "FUCKYOU":
nick = "Zodiac"
self.bot.privmsg(nick, message)
self.count += 1
self.bot.log.info(f"Sent to {nick} ({self.count}/{total})")
except Exception as e:
self.bot.log.error(f"Failed to message {nick}: {e}")
finally:
self.queue.task_done() # Mark the task as done after processing
if self.delay > 0:
await asyncio.sleep(self.delay)
except asyncio.CancelledError:
# Exit silently on cancellation
break
except Exception as e:
self.bot.log.error(f"Worker error: {e}")
break
@command(permission="admin", options_first=True)
async def msgall(self, mask, target, args):
"""
Send a message to all users in the channel using async queue.
%%msgall <message>...
This command can only be used in a channel.
"""
if not target.is_channel:
return "This command can only be used in a channel."
message = ' '.join(args['<message>'])
workers = [] # Ensure workers is defined in the scope of the try block
try:
# Fetch the list of users in the channel
result = await self.bot.async_cmds.names(target)
nicknames = [strip_nick_prefix(n) for n in result['names']]
recipients = [n for n in nicknames if n != self.bot.nick]
if not recipients:
return "No valid recipients found."
total = len(recipients)
self.count = 0 # Reset the counter for this run
# Add all recipients to the queue
for nick in recipients:
await self.queue.put(nick)
# Create worker tasks
workers = [asyncio.create_task(self._worker(message, total)) for _ in range(1)]
# Send initial confirmation
self.bot.privmsg(target, f"Starting mass message to {total} users...")
# Wait for the queue to be fully processed
await self.queue.join()
# Cancel worker tasks after the queue is empty
for task in workers:
task.cancel()
# Wait for workers to finish cancellation
await asyncio.gather(*workers, return_exceptions=True)
return f"Mass message completed. Sent to {self.count}/{total} users."
except asyncio.CancelledError:
self.bot.log.info("Mass message command cancelled. Cleaning up workers.")
# Cancel any existing worker tasks
for task in workers:
task.cancel()
# Allow workers to handle cancellation
await asyncio.gather(*workers, return_exceptions=True)
# Re-raise the cancellation to inform the bot framework
raise
except Exception as e:
self.bot.log.error(f"Error in msgall: {e}")
return f"Mass message failed: {str(e)}"