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