2025-02-13 06:35:15 +00:00
|
|
|
# -*- 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)
|
|
|
|
"""
|
|
|
|
|
2025-02-13 04:55:42 +00:00
|
|
|
import irc3
|
|
|
|
from irc3.plugins.command import command
|
|
|
|
from irc3.compat import Queue
|
|
|
|
import asyncio
|
|
|
|
|
2025-02-13 06:35:15 +00:00
|
|
|
|
2025-02-13 04:55:42 +00:00
|
|
|
def strip_nick_prefix(nick):
|
2025-02-13 06:35:15 +00:00
|
|
|
"""Remove IRC mode prefixes from a nickname."""
|
2025-02-13 04:55:42 +00:00
|
|
|
return nick.lstrip('@+%&~!') if nick else ''
|
|
|
|
|
2025-02-13 06:35:15 +00:00
|
|
|
|
2025-02-13 04:55:42 +00:00
|
|
|
@irc3.plugin
|
|
|
|
class MassMessagePlugin:
|
2025-02-13 06:35:15 +00:00
|
|
|
"""Mass messaging plugin using async queue system."""
|
2025-02-13 04:55:42 +00:00
|
|
|
|
|
|
|
def __init__(self, bot):
|
2025-02-13 06:35:15 +00:00
|
|
|
"""
|
|
|
|
Initialize the plugin with bot reference.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
bot (irc3.IrcBot): The IRC bot instance.
|
|
|
|
"""
|
2025-02-13 04:55:42 +00:00
|
|
|
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):
|
2025-02-13 06:35:15 +00:00
|
|
|
"""
|
|
|
|
Worker task to process messages from the queue.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
message (str): The message to send.
|
|
|
|
total (int): The total number of recipients.
|
|
|
|
"""
|
2025-02-13 04:55:42 +00:00
|
|
|
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):
|
2025-02-13 06:35:15 +00:00
|
|
|
"""
|
|
|
|
Send a message to all users in the channel using async queue.
|
2025-02-13 04:55:42 +00:00
|
|
|
|
|
|
|
%%msgall <message>...
|
2025-02-13 06:35:15 +00:00
|
|
|
|
|
|
|
This command can only be used in a channel.
|
2025-02-13 04:55:42 +00:00
|
|
|
"""
|
|
|
|
if not target.is_channel:
|
2025-02-13 06:35:15 +00:00
|
|
|
return "This command can only be used in a channel."
|
2025-02-13 04:55:42 +00:00
|
|
|
|
|
|
|
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:
|
2025-02-13 06:35:15 +00:00
|
|
|
return "No valid recipients found."
|
2025-02-13 04:55:42 +00:00
|
|
|
|
|
|
|
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)}"
|