[irc] Plugin rename/refactor.

The `chat.irc` plugin is now `irc`.
This commit is contained in:
Fabio Manganiello 2024-03-01 21:20:33 +01:00
parent 1ba85231d8
commit 7637890a54
Signed by: blacklight
GPG key ID: D90FBA7F76362774
6 changed files with 207 additions and 104 deletions

View file

@ -1,5 +0,0 @@
``chat.irc``
============
.. automodule:: platypush.plugins.chat.irc
:members:

View file

@ -0,0 +1,5 @@
``irc``
=======
.. automodule:: platypush.plugins.irc
:members:

View file

@ -22,7 +22,6 @@ Plugins
platypush/plugins/camera.ir.mlx90640.rst platypush/plugins/camera.ir.mlx90640.rst
platypush/plugins/camera.pi.rst platypush/plugins/camera.pi.rst
platypush/plugins/camera.pi.legacy.rst platypush/plugins/camera.pi.legacy.rst
platypush/plugins/chat.irc.rst
platypush/plugins/chat.telegram.rst platypush/plugins/chat.telegram.rst
platypush/plugins/clipboard.rst platypush/plugins/clipboard.rst
platypush/plugins/config.rst platypush/plugins/config.rst
@ -55,6 +54,7 @@ Plugins
platypush/plugins/http.webpage.rst platypush/plugins/http.webpage.rst
platypush/plugins/ifttt.rst platypush/plugins/ifttt.rst
platypush/plugins/inspect.rst platypush/plugins/inspect.rst
platypush/plugins/irc.rst
platypush/plugins/joystick.rst platypush/plugins/joystick.rst
platypush/plugins/kafka.rst platypush/plugins/kafka.rst
platypush/plugins/lastfm.rst platypush/plugins/lastfm.rst

View file

@ -2,6 +2,7 @@ import os
from typing import Sequence, Dict, Tuple, Union, Optional from typing import Sequence, Dict, Tuple, Union, Optional
from platypush.plugins import RunnablePlugin, action from platypush.plugins import RunnablePlugin, action
from platypush.plugins.chat import ChatPlugin
from platypush.schemas.irc import ( from platypush.schemas.irc import (
IRCServerSchema, IRCServerSchema,
IRCServerStatusSchema, IRCServerStatusSchema,
@ -9,10 +10,9 @@ from platypush.schemas.irc import (
) )
from ._bot import IRCBot from ._bot import IRCBot
from .. import ChatPlugin
class ChatIrcPlugin(RunnablePlugin, ChatPlugin): class IrcPlugin(RunnablePlugin, ChatPlugin):
""" """
IRC integration. IRC integration.
@ -48,7 +48,7 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
try: try:
self._bots: Dict[Tuple[str, int], IRCBot] = { self._bots: Dict[Tuple[str, int], IRCBot] = {
(server_conf['server'], server_conf['port']): IRCBot(**server_conf) (server_conf['server'], server_conf['port']): IRCBot(**server_conf)
for server_conf in IRCServerSchema().load(servers, many=True) for server_conf in IRCServerSchema().load(servers, many=True) # type: ignore
} }
except Exception as e: except Exception as e:
self.logger.warning(f'Could not load IRC server configuration: {e}') self.logger.warning(f'Could not load IRC server configuration: {e}')
@ -57,15 +57,15 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@property @property
def _bots_by_server(self) -> Dict[str, IRCBot]: def _bots_by_server(self) -> Dict[str, IRCBot]:
return {bot.server: bot for srv, bot in self._bots.items()} return {bot.server: bot for bot in self._bots.values()}
@property @property
def _bots_by_server_and_port(self) -> Dict[Tuple[str, int], IRCBot]: def _bots_by_server_and_port(self) -> Dict[Tuple[str, int], IRCBot]:
return {(bot.server, bot.port): bot for srv, bot in self._bots.items()} return {(bot.server, bot.port): bot for bot in self._bots.values()}
@property @property
def _bots_by_alias(self) -> Dict[str, IRCBot]: def _bots_by_alias(self) -> Dict[str, IRCBot]:
return {bot.alias: bot for srv, bot in self._bots.items() if bot.alias} return {bot.alias: bot for bot in self._bots.values() if bot.alias}
def main(self): def main(self):
self._connect() self._connect()
@ -88,7 +88,7 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
def _get_bot(self, server: Union[str, Tuple[str, int]]) -> IRCBot: def _get_bot(self, server: Union[str, Tuple[str, int]]) -> IRCBot:
if isinstance(server, (tuple, list, set)): if isinstance(server, (tuple, list, set)):
bot = self._bots_by_server_and_port[tuple(server)] bot = self._bots_by_server_and_port[tuple(server)] # type: ignore
else: else:
bot = self._bots_by_alias.get(server, self._bots_by_server.get(server)) bot = self._bots_by_alias.get(server, self._bots_by_server.get(server))
@ -184,22 +184,24 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
""" """
bot = self._get_bot(server) bot = self._get_bot(server)
channel_name = channel channel_name = channel
channel = bot.channels.get(channel) ch = bot.channels.get(channel)
assert channel, f'Not connected to channel {channel}' assert ch, f'Not connected to channel {channel}'
return IRCChannelSchema().dump( return dict(
IRCChannelSchema().dump(
{ {
'is_invite_only': channel.is_invite_only(), 'is_invite_only': ch.is_invite_only(),
'is_moderated': channel.is_moderated(), 'is_moderated': ch.is_moderated(),
'is_protected': channel.is_protected(), 'is_protected': ch.is_protected(),
'is_secret': channel.is_secret(), 'is_secret': ch.is_secret(),
'name': channel_name, 'name': channel_name,
'modes': channel.modes, 'modes': ch.modes,
'opers': list(channel.opers()), 'opers': list(ch.opers()),
'owners': channel.owners(), 'owners': ch.owners(),
'users': list(channel.users()), 'users': list(ch.users()),
'voiced': list(channel.voiced()), 'voiced': list(ch.voiced()),
} }
) )
)
@action @action
def send_ctcp_message( def send_ctcp_message(

View file

@ -8,32 +8,61 @@ import select
import socket import socket
import ssl as _ssl import ssl as _ssl
import struct import struct
from typing import Iterable, Optional, Type, Sequence, Dict, Tuple from typing import Generator, Iterable, Optional, Type, Sequence, Dict, Tuple
import irc.bot import irc.bot
from irc.client import Connection, Event as _IRCEvent, ServerConnection, DCCConnection, ip_numstr_to_quad, \ from irc.client import (
ip_quad_to_numstr Connection,
Event as _IRCEvent,
ServerConnection,
DCCConnection,
ip_numstr_to_quad,
ip_quad_to_numstr,
)
from irc.connection import Factory as ConnectionFactory from irc.connection import Factory as ConnectionFactory
from irc.events import codes as irc_event_codes from irc.events import codes as irc_event_codes
from platypush.context import get_bus from platypush.context import get_bus
from platypush.message.event.irc import IRCEvent, IRCChannelJoinEvent, IRCChannelKickEvent, IRCDisconnectEvent, \ from platypush.message.event.irc import (
IRCModeEvent, IRCNickChangeEvent, IRCPartEvent, IRCQuitEvent, IRCConnectEvent, IRCPrivateMessageEvent, \ IRCEvent,
IRCPublicMessageEvent, IRCDCCRequestEvent, IRCDCCMessageEvent, IRCDCCFileRequestEvent, IRCCTCPMessageEvent, \ IRCChannelJoinEvent,
IRCDCCFileRecvCompletedEvent, IRCDCCFileRecvCancelledEvent, IRCDCCFileSendCompletedEvent, \ IRCChannelKickEvent,
IRCDCCFileSendCancelledEvent IRCDisconnectEvent,
IRCModeEvent,
IRCNickChangeEvent,
IRCPartEvent,
IRCQuitEvent,
IRCConnectEvent,
IRCPrivateMessageEvent,
IRCPublicMessageEvent,
IRCDCCRequestEvent,
IRCDCCMessageEvent,
IRCDCCFileRequestEvent,
IRCCTCPMessageEvent,
IRCDCCFileRecvCompletedEvent,
IRCDCCFileRecvCancelledEvent,
IRCDCCFileSendCompletedEvent,
IRCDCCFileSendCancelledEvent,
)
class IRCBot(irc.bot.SingleServerIRCBot): class IRCBot(irc.bot.SingleServerIRCBot):
def __init__( def __init__(
self, server: str, port: int, nickname: str, alias: str, self,
server: str,
port: int,
nickname: str,
alias: str,
channels: Iterable[str], channels: Iterable[str],
response_timeout: Optional[float], response_timeout: Optional[float],
dcc_file_transfer_timeout: Optional[float], dcc_file_transfer_timeout: Optional[float],
dcc_accept_timeout: Optional[float], dcc_accept_timeout: Optional[float],
dcc_downloads_dir: str, realname: Optional[str] = None, dcc_downloads_dir: str,
password: Optional[str] = None, ssl: bool = False, realname: Optional[str] = None,
ipv6: bool = False, stop_message: Optional[str] = None, password: Optional[str] = None,
ssl: bool = False,
ipv6: bool = False,
stop_message: Optional[str] = None,
dcc_ip_whitelist: Optional[Sequence[str]] = None, dcc_ip_whitelist: Optional[Sequence[str]] = None,
dcc_ip_blacklist: Optional[Sequence[str]] = None, dcc_ip_blacklist: Optional[Sequence[str]] = None,
dcc_nick_whitelist: Optional[Sequence[str]] = None, dcc_nick_whitelist: Optional[Sequence[str]] = None,
@ -47,8 +76,10 @@ class IRCBot(irc.bot.SingleServerIRCBot):
connection_factory.family = socket.AF_INET6 connection_factory.family = socket.AF_INET6
super().__init__( super().__init__(
server_list=[(server, port)], nickname=nickname, server_list=[(server, port)],
realname=realname, connect_factory=connection_factory nickname=nickname,
realname=realname,
connect_factory=connection_factory,
) )
self.server = server self.server = server
@ -78,7 +109,9 @@ class IRCBot(irc.bot.SingleServerIRCBot):
def stop_message(self) -> Optional[str]: def stop_message(self) -> Optional[str]:
return self._stop_message return self._stop_message
def _post_event(self, connection: Connection, output_event_type: Type[IRCEvent], **kwargs): def _post_event(
self, connection: Connection, output_event_type: Type[IRCEvent], **kwargs
):
if isinstance(connection, ServerConnection): if isinstance(connection, ServerConnection):
kwargs['server'] = connection.server kwargs['server'] = connection.server
kwargs['port'] = connection.port kwargs['port'] = connection.port
@ -89,26 +122,39 @@ class IRCBot(irc.bot.SingleServerIRCBot):
get_bus().post(event) get_bus().post(event)
def on_join(self, connection: ServerConnection, event: _IRCEvent): def on_join(self, connection: ServerConnection, event: _IRCEvent):
self._post_event(connection, IRCChannelJoinEvent, channel=event.target, nick=event.source.nick) self._post_event(
connection,
IRCChannelJoinEvent,
channel=event.target,
nick=event.source.nick,
)
super()._on_join(connection, event) super()._on_join(connection, event)
def on_kick(self, connection: ServerConnection, event: _IRCEvent): def on_kick(self, connection: ServerConnection, event: _IRCEvent):
self._post_event( self._post_event(
connection, IRCChannelKickEvent, channel=event.target, connection,
source_nick=event.arguments[1], target_nick=event.arguments[0] IRCChannelKickEvent,
channel=event.target,
source_nick=event.arguments[1],
target_nick=event.arguments[0],
) )
super()._on_kick(connection, event) super()._on_kick(connection, event)
def on_nick(self, connection: ServerConnection, event: _IRCEvent): def on_nick(self, connection: ServerConnection, event: _IRCEvent):
self._post_event( self._post_event(
connection, IRCNickChangeEvent, before=event.source.nick, after=event.target.nick connection,
IRCNickChangeEvent,
before=event.source.nick,
after=event.target.nick,
) )
super()._on_nick(connection, event) super()._on_nick(connection, event)
def on_mode(self, connection: ServerConnection, event: _IRCEvent): def on_mode(self, connection: ServerConnection, event: _IRCEvent):
self._post_event( self._post_event(
connection, IRCModeEvent, mode=event.arguments[0], connection,
IRCModeEvent,
mode=event.arguments[0],
source=event.source.nick, source=event.source.nick,
target_=(event.arguments[1] if len(event.arguments) > 1 else None), target_=(event.arguments[1] if len(event.arguments) > 1 else None),
channel=event.target, channel=event.target,
@ -140,18 +186,22 @@ class IRCBot(irc.bot.SingleServerIRCBot):
def on_pubmsg(self, connection: ServerConnection, event: _IRCEvent): def on_pubmsg(self, connection: ServerConnection, event: _IRCEvent):
self._post_event( self._post_event(
connection, IRCPublicMessageEvent, connection,
text=event.arguments[0], nick=event.source.nick, IRCPublicMessageEvent,
text=event.arguments[0],
nick=event.source.nick,
channel=event.target, channel=event.target,
mentions_me=self._mentions_me(connection, event.arguments[0]) mentions_me=self._mentions_me(connection, event.arguments[0]),
) )
def on_privmsg(self, connection: ServerConnection, event: _IRCEvent): def on_privmsg(self, connection: ServerConnection, event: _IRCEvent):
self._post_event( self._post_event(
connection, IRCPrivateMessageEvent, connection,
text=event.arguments[0], nick=event.source.nick, IRCPrivateMessageEvent,
text=event.arguments[0],
nick=event.source.nick,
channel=event.target, channel=event.target,
mentions_me=self._mentions_me(connection, event.arguments[0]) mentions_me=self._mentions_me(connection, event.arguments[0]),
) )
def on_dccchat(self, connection: DCCConnection, event: _IRCEvent): def on_dccchat(self, connection: DCCConnection, event: _IRCEvent):
@ -168,7 +218,9 @@ class IRCBot(irc.bot.SingleServerIRCBot):
nick = event.source.nick nick = event.source.nick
if not self._is_dcc_connection_request_allowed(address=address, nick=nick): if not self._is_dcc_connection_request_allowed(address=address, nick=nick):
self.logger.info(f'Refused DCC connection from address={address} nick={nick}') self.logger.info(
f'Refused DCC connection from address={address} nick={nick}'
)
connection.disconnect('Unauthorized peer') connection.disconnect('Unauthorized peer')
return return
@ -194,15 +246,23 @@ class IRCBot(irc.bot.SingleServerIRCBot):
conn_map = getattr(self, f'_dcc_{proc_type}_procs', {}) conn_map = getattr(self, f'_dcc_{proc_type}_procs', {})
conn_map.pop((addr, port), None) conn_map.pop((addr, port), None)
def _process_dcc_recv(self, connection: DCCConnection, filename: str, address: str, port: int, size: int): def _process_dcc_recv(
self,
connection: DCCConnection,
filename: str,
address: str,
port: int,
size: int,
):
pathlib.Path(self.dcc_downloads_dir).mkdir(parents=True, exist_ok=True) pathlib.Path(self.dcc_downloads_dir).mkdir(parents=True, exist_ok=True)
filename = os.path.abspath(os.path.join(self.dcc_downloads_dir, filename)) filename = os.path.abspath(os.path.join(self.dcc_downloads_dir, filename))
assert filename.startswith(self.dcc_downloads_dir), ( assert filename.startswith(
'Attempt to save a file outside the downloads directory' self.dcc_downloads_dir
) ), 'Attempt to save a file outside the downloads directory'
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock, \ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock, open(
open(filename, 'wb') as f: filename, 'wb'
) as f:
try: try:
sock.connect((address, port)) sock.connect((address, port))
processed_bytes = 0 processed_bytes = 0
@ -214,15 +274,23 @@ class IRCBot(irc.bot.SingleServerIRCBot):
sock.send(f'{socket.htonl(processed_bytes)}'.encode()) sock.send(f'{socket.htonl(processed_bytes)}'.encode())
self._post_event( self._post_event(
connection, IRCDCCFileRecvCompletedEvent, address=address, connection,
port=port, file=filename, size=size IRCDCCFileRecvCompletedEvent,
address=address,
port=port,
file=filename,
size=size,
) )
except Exception as e: except Exception as e:
self.logger.error(f'DCC transfer error from {address}:{port}: {e}') self.logger.error(f'DCC transfer error from {address}:{port}: {e}')
self.logger.exception(e) self.logger.exception(e)
self._post_event( self._post_event(
connection, IRCDCCFileRecvCancelledEvent, address=address, connection,
port=port, file=filename, error=str(e) IRCDCCFileRecvCancelledEvent,
address=address,
port=port,
file=filename,
error=str(e),
) )
finally: finally:
connection.disconnect() connection.disconnect()
@ -265,22 +333,33 @@ class IRCBot(irc.bot.SingleServerIRCBot):
buf = f.read(4096) buf = f.read(4096)
self._post_event( self._post_event(
connection, IRCDCCFileSendCompletedEvent, connection,
file=filename, address=address, port=transfer_port IRCDCCFileSendCompletedEvent,
file=filename,
address=address,
port=transfer_port,
) )
finally: finally:
client_sock.close() client_sock.close()
dcc_sock.close() dcc_sock.close()
except Exception as e: except Exception as e:
self.logger.error(f'DCC transfer error to {connection.peeraddress}:{connection.peerport}: {e}') self.logger.error(
f'DCC transfer error to {connection.peeraddress}:{connection.peerport}: {e}'
)
self.logger.exception(e) self.logger.exception(e)
self._post_event( self._post_event(
connection, IRCDCCFileSendCancelledEvent, address=connection.peeraddress, connection,
port=connection.peerport, file=filename, error=str(e) IRCDCCFileSendCancelledEvent,
address=connection.peeraddress,
port=connection.peerport,
file=filename,
error=str(e),
) )
finally: finally:
connection.disconnect() connection.disconnect()
self._dcc_proc_completion_queue.put(('send', connection.localaddress, connection.localport)) self._dcc_proc_completion_queue.put(
('send', connection.localaddress, connection.localport)
)
def _set_accept_timeout(self, sock: socket.socket): def _set_accept_timeout(self, sock: socket.socket):
if self.dcc_accept_timeout: if self.dcc_accept_timeout:
@ -293,12 +372,10 @@ class IRCBot(irc.bot.SingleServerIRCBot):
if proc and proc.is_alive(): if proc and proc.is_alive():
proc.terminate() proc.terminate()
def _accept_dcc_file_request(self, connection: DCCConnection, ctcp_msg: str, nick: str): def _accept_dcc_file_request(
ctcp_msg = [ self, connection: DCCConnection, ctcp_msg: str, nick: str
token.strip() ):
for token in ctcp_msg.split(' ') ctcp_msg = [token.strip() for token in ctcp_msg.split(' ') if token]
if token
]
filename = ' '.join(ctcp_msg[2:-3]) filename = ' '.join(ctcp_msg[2:-3])
address, port, size = ctcp_msg[-3:] address, port, size = ctcp_msg[-3:]
@ -315,22 +392,26 @@ class IRCBot(irc.bot.SingleServerIRCBot):
return return
self._post_event( self._post_event(
connection, IRCDCCFileRequestEvent, connection,
IRCDCCFileRequestEvent,
address=address, address=address,
port=port, file=filename, port=port,
size=size, nick=nick file=filename,
size=size,
nick=nick,
) )
# Accept the file request # Accept the file request
# (if we're here then the peer is whitelisted/not blacklisted) # (if we're here then the peer is whitelisted/not blacklisted)
self._dcc_recv_procs[(address, port)] = multiprocessing.Process( self._dcc_recv_procs[(address, port)] = multiprocessing.Process(
target=self._process_dcc_recv, kwargs={ target=self._process_dcc_recv,
kwargs={
'connection': connection, 'connection': connection,
'address': address, 'address': address,
'port': port, 'port': port,
'filename': filename, 'filename': filename,
'size': size, 'size': size,
} },
) )
self._dcc_recv_procs[(address, port)].start() self._dcc_recv_procs[(address, port)].start()
@ -364,7 +445,12 @@ class IRCBot(irc.bot.SingleServerIRCBot):
if event.arguments[0][: len(ctcp_header)] == ctcp_header: if event.arguments[0][: len(ctcp_header)] == ctcp_header:
return self._handle_ctcp_message(connection, event) return self._handle_ctcp_message(connection, event)
self._post_event(connection, IRCDCCMessageEvent, address=event.source, body=event.arguments[0]) self._post_event(
connection,
IRCDCCMessageEvent,
address=event.source,
body=event.arguments[0],
)
def on_disconnect(self, connection: Connection, event: _IRCEvent): def on_disconnect(self, connection: Connection, event: _IRCEvent):
self._post_event(connection, IRCDisconnectEvent) self._post_event(connection, IRCDisconnectEvent)
@ -385,7 +471,9 @@ class IRCBot(irc.bot.SingleServerIRCBot):
q.put(event) q.put(event)
def _on_generic_event(self, _, event: _IRCEvent): def _on_generic_event(self, _, event: _IRCEvent):
self.logger.debug(f'Received raw unhandled IRC event on {self.server}: {event.__dict__}') self.logger.debug(
f'Received raw unhandled IRC event on {self.server}: {event.__dict__}'
)
def _is_dcc_connection_request_allowed(self, address: str, nick: str) -> bool: def _is_dcc_connection_request_allowed(self, address: str, nick: str) -> bool:
if self.dcc_ip_whitelist and address not in self.dcc_ip_whitelist: if self.dcc_ip_whitelist and address not in self.dcc_ip_whitelist:
@ -397,7 +485,10 @@ class IRCBot(irc.bot.SingleServerIRCBot):
if self.dcc_nick_blacklist and nick in self.dcc_nick_blacklist: if self.dcc_nick_blacklist and nick in self.dcc_nick_blacklist:
return False return False
if self.dcc_max_connections and len(self._dcc_recv_procs) >= self.dcc_max_connections: if (
self.dcc_max_connections
and len(self._dcc_recv_procs) >= self.dcc_max_connections
):
self.logger.warning( self.logger.warning(
f'Refused new DCC connection: maximum number of concurrent ' f'Refused new DCC connection: maximum number of concurrent '
f'connections ({self.dcc_max_connections}) reached' f'connections ({self.dcc_max_connections}) reached'
@ -407,29 +498,35 @@ class IRCBot(irc.bot.SingleServerIRCBot):
return True return True
def dcc_file_transfer(self, file: str, nick: str, bind_address: Optional[str] = None): def dcc_file_transfer(
self, file: str, nick: str, bind_address: Optional[str] = None
):
conn: DCCConnection = self.dcc('chat') conn: DCCConnection = self.dcc('chat')
bind_address = (bind_address, 0) if bind_address else None bind_address = (bind_address, 0) if bind_address else None
conn.listen(bind_address) conn.listen(bind_address)
conn.passive = False conn.passive = False
self.connection.privmsg( self.connection.privmsg(
nick, nick,
f'\x01DCC CHAT chat {ip_quad_to_numstr(conn.localaddress)} {conn.localport}\x01' f'\x01DCC CHAT chat {ip_quad_to_numstr(conn.localaddress)} {conn.localport}\x01',
) )
send_proc = self._dcc_send_procs[(conn.localaddress, conn.localport)] = multiprocessing.Process( send_proc = self._dcc_send_procs[
(conn.localaddress, conn.localport)
] = multiprocessing.Process(
target=self._process_dcc_send, target=self._process_dcc_send,
kwargs={ kwargs={
'connection': conn, 'connection': conn,
'filename': file, 'filename': file,
} },
) )
send_proc.start() send_proc.start()
send_proc.join() send_proc.join()
@contextlib.contextmanager @contextlib.contextmanager
def event_queue(self, event_type: str) -> multiprocessing.Queue: def event_queue(
self, event_type: str
) -> Generator[multiprocessing.Queue, None, None]:
q = self._pending_requests[event_type] = multiprocessing.Queue() q = self._pending_requests[event_type] = multiprocessing.Queue()
try: try:
yield q yield q
@ -438,7 +535,9 @@ class IRCBot(irc.bot.SingleServerIRCBot):
self._pending_requests.pop(event_type, None) self._pending_requests.pop(event_type, None)
def start(self): def start(self):
self._dcc_processor = multiprocessing.Process(target=self._dcc_connect_processor) self._dcc_processor = multiprocessing.Process(
target=self._dcc_connect_processor
)
self._dcc_processor.start() self._dcc_processor.start()
for event_code in irc_event_codes.keys(): for event_code in irc_event_codes.keys():
@ -452,7 +551,9 @@ class IRCBot(irc.bot.SingleServerIRCBot):
def stop(self, msg: Optional[str] = None): def stop(self, msg: Optional[str] = None):
msg = msg or self.stop_message msg = msg or self.stop_message
for addr, port in list(self._dcc_recv_procs.keys()) + list(self._dcc_send_procs.keys()): for addr, port in list(self._dcc_recv_procs.keys()) + list(
self._dcc_send_procs.keys()
):
self._stop_dcc_connection(addr, port) self._stop_dcc_connection(addr, port)
if self._dcc_processor and self._dcc_processor.is_alive(): if self._dcc_processor and self._dcc_processor.is_alive():

View file

@ -18,7 +18,7 @@ manifest:
platypush.message.event.irc.IRCDCCFileRecvCancelledEvent: when a DCC file download is cancelled platypush.message.event.irc.IRCDCCFileRecvCancelledEvent: when a DCC file download is cancelled
platypush.message.event.irc.IRCDCCFileSendCompletedEvent: when a DCC file upload is completed platypush.message.event.irc.IRCDCCFileSendCompletedEvent: when a DCC file upload is completed
platypush.message.event.irc.IRCDCCFileSendCancelledEvent: when a DCC file upload is cancelled platypush.message.event.irc.IRCDCCFileSendCancelledEvent: when a DCC file upload is cancelled
package: platypush.plugins.chat.irc package: platypush.plugins.irc
type: plugin type: plugin
install: install:
apt: apt: