forked from platypush/platypush
[irc] Plugin rename/refactor.
The `chat.irc` plugin is now `irc`.
This commit is contained in:
parent
1ba85231d8
commit
7637890a54
6 changed files with 207 additions and 104 deletions
|
@ -1,5 +0,0 @@
|
||||||
``chat.irc``
|
|
||||||
============
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.chat.irc
|
|
||||||
:members:
|
|
5
docs/source/platypush/plugins/irc.rst
Normal file
5
docs/source/platypush/plugins/irc.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``irc``
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.irc
|
||||||
|
:members:
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
|
@ -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()
|
||||||
|
@ -342,7 +423,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = [
|
msg = [
|
||||||
body[(0 if i == 0 else sep_pos[i - 1] + 1):pos].decode().strip()
|
body[(0 if i == 0 else sep_pos[i - 1] + 1) : pos].decode().strip()
|
||||||
for i, pos in enumerate(sep_pos)
|
for i, pos in enumerate(sep_pos)
|
||||||
][1:]
|
][1:]
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
|
@ -361,10 +442,15 @@ class IRCBot(irc.bot.SingleServerIRCBot):
|
||||||
|
|
||||||
def on_dccmsg(self, connection: DCCConnection, event: _IRCEvent):
|
def on_dccmsg(self, connection: DCCConnection, event: _IRCEvent):
|
||||||
ctcp_header = b'CTCP_MESSAGE'
|
ctcp_header = b'CTCP_MESSAGE'
|
||||||
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():
|
|
@ -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:
|
Loading…
Reference in a new issue