14 changed files with 20 additions and 270 deletions
@ -1,126 +0,0 @@ |
|||
# Copyright (c) 2016-2017, Neil Booth |
|||
# |
|||
# All rights reserved. |
|||
# |
|||
# See the file "LICENCE" for information about the copyright |
|||
# and warranty status of this software. |
|||
|
|||
'''IRC connectivity to discover peers. |
|||
|
|||
Only calling start() requires the IRC Python module. |
|||
''' |
|||
|
|||
import asyncio |
|||
import re |
|||
|
|||
from collections import namedtuple |
|||
|
|||
from lib.hash import double_sha256 |
|||
from lib.util import LoggedClass |
|||
|
|||
|
|||
class IRC(LoggedClass): |
|||
|
|||
class DisconnectedError(Exception): |
|||
pass |
|||
|
|||
def __init__(self, env, peer_mgr): |
|||
super().__init__() |
|||
self.coin = env.coin |
|||
self.peer_mgr = peer_mgr |
|||
|
|||
# If this isn't something a peer or client expects |
|||
# then you won't appear in the client's network dialog box |
|||
self.channel = env.coin.IRC_CHANNEL |
|||
self.prefix = env.coin.IRC_PREFIX |
|||
self.nick = '{}{}'.format(self.prefix, |
|||
env.irc_nick if env.irc_nick else |
|||
double_sha256(env.host.encode()) |
|||
[:5].hex()) |
|||
self.peer_regexp = re.compile('({}[^!]*)!'.format(self.prefix)) |
|||
|
|||
async def start(self, name_pairs): |
|||
'''Start IRC connections if enabled in environment.''' |
|||
import irc.client as irc_client |
|||
from jaraco.stream import buffer |
|||
|
|||
# see https://pypi.python.org/pypi/irc under DecodingInput |
|||
irc_client.ServerConnection.buffer_class = \ |
|||
buffer.LenientDecodingLineBuffer |
|||
|
|||
# Register handlers for events we're interested in |
|||
reactor = irc_client.Reactor() |
|||
for event in 'welcome join whoreply disconnect'.split(): |
|||
reactor.add_global_handler(event, getattr(self, 'on_' + event)) |
|||
|
|||
# Note: Multiple nicks in same channel will trigger duplicate events |
|||
clients = [IrcClient(self.coin, real_name, self.nick + suffix, |
|||
reactor.server()) |
|||
for (real_name, suffix) in name_pairs] |
|||
|
|||
while True: |
|||
try: |
|||
for client in clients: |
|||
client.connect(self) |
|||
while True: |
|||
reactor.process_once() |
|||
await asyncio.sleep(2) |
|||
except irc_client.ServerConnectionError as e: |
|||
self.logger.error('connection error: {}'.format(e)) |
|||
except self.DisconnectedError: |
|||
self.logger.error('disconnected') |
|||
await asyncio.sleep(10) |
|||
|
|||
def log_event(self, event): |
|||
self.logger.info('IRC event type {} source {} args {}' |
|||
.format(event.type, event.source, event.arguments)) |
|||
|
|||
def on_welcome(self, connection, event): |
|||
'''Called when we connect to irc server.''' |
|||
connection.join(self.channel) |
|||
|
|||
def on_disconnect(self, connection, event): |
|||
'''Called if we are disconnected.''' |
|||
self.log_event(event) |
|||
raise self.DisconnectedError |
|||
|
|||
def on_join(self, connection, event): |
|||
'''Called when someone new connects to our channel, including us.''' |
|||
# /who the channel when we join. We used to /who on each |
|||
# namreply event, but the IRC server would frequently kick us |
|||
# for flooding. This requests only once including the tor case. |
|||
if event.source.startswith(self.nick + '!'): |
|||
connection.who(self.channel) |
|||
else: |
|||
match = self.peer_regexp.match(event.source) |
|||
if match: |
|||
connection.who(match.group(1)) |
|||
|
|||
def on_whoreply(self, connection, event): |
|||
'''Called when a response to our who requests arrives. |
|||
|
|||
The nick is the 4th argument, and real name is in the 6th |
|||
argument preceeded by '0 ' for some reason. |
|||
''' |
|||
nick = event.arguments[4] |
|||
if nick.startswith(self.prefix): |
|||
line = event.arguments[6].split() |
|||
hp_string = ' '.join(line[1:]) # hostname, ports, version etc. |
|||
self.peer_mgr.add_irc_peer(nick, hp_string) |
|||
|
|||
|
|||
class IrcClient(object): |
|||
|
|||
def __init__(self, coin, real_name, nick, server): |
|||
self.irc_host = coin.IRC_SERVER |
|||
self.irc_port = coin.IRC_PORT |
|||
self.nick = nick |
|||
self.real_name = real_name |
|||
self.server = server |
|||
|
|||
def connect(self, irc): |
|||
'''Connect this client to its IRC server''' |
|||
irc.logger.info('joining {} as "{}" with real name "{}"' |
|||
.format(irc.channel, self.nick, self.real_name)) |
|||
self.server.connect(self.irc_host, self.irc_port, self.nick, |
|||
ircname=self.real_name) |
Loading…
Reference in new issue