Passed
Push — main ( fabcc0...a6ad84 )
by Jochen
01:52
created

weitersager.irc.DummyBot.disconnect()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 2
ccs 1
cts 2
cp 0.5
crap 1.125
rs 10
c 0
b 0
f 0
1
"""
2
weitersager.irc
3
~~~~~~~~~~~~~~~
4
5
Internet Relay Chat
6
7
:Copyright: 2007-2021 Jochen Kupperschmidt
8
:License: MIT, see LICENSE for details.
9
"""
10
11 1
import ssl
12 1
from typing import Any, List, Optional, Union
13
14 1
from irc.bot import ServerSpec, SingleServerIRCBot
15 1
from irc.connection import Factory
16 1
from jaraco.stream.buffer import LenientDecodingLineBuffer
17
18 1
from .config import IrcChannel, IrcConfig, IrcServer
19 1
from .signals import channel_joined
20 1
from .util import log, start_thread
21
22
23 1
class Bot(SingleServerIRCBot):
24
    """An IRC bot to forward messages to IRC channels."""
25
26 1
    def __init__(
27
        self,
28
        server: IrcServer,
29
        nickname: str,
30
        realname: str,
31
        channels: List[IrcChannel],
32
    ) -> None:
33
        log('Connecting to IRC server {0.host}:{0.port:d} ...', server)
34
35
        server_spec = ServerSpec(server.host, server.port, server.password)
36
        factory = Factory(wrapper=ssl.wrap_socket) if server.ssl else Factory()
37
        SingleServerIRCBot.__init__(
38
            self, [server_spec], nickname, realname, connect_factory=factory
39
        )
40
41
        if server.rate_limit is not None:
42
            log(
43
                'IRC send rate limit set to {:.2f} messages per second.',
44
                server.rate_limit,
45
            )
46
            self.connection.set_rate_limit(server.rate_limit)
47
        else:
48
            log('No IRC send rate limit set.')
49
50
        # Avoid `UnicodeDecodeError` on non-UTF-8 messages.
51
        self.connection.buffer_class = LenientDecodingLineBuffer
52
53
        # Note: `self.channels` already exists in super class.
54
        self.channels_to_join = channels
55
56 1
    def start(self) -> None:
57
        """Connect to the server, in a separate thread."""
58
        start_thread(super().start, self.__class__.__name__)
59
60 1
    def get_version(self):
61
        return 'Weitersager'
62
63 1
    def on_welcome(self, conn, event) -> None:
64
        """Join channels after connect."""
65
        log('Connected to {}:{:d}.', *conn.socket.getpeername())
66
67
        channel_names = sorted(c.name for c in self.channels_to_join)
68
        log('Channels to join: {}', ', '.join(channel_names))
69
70
        for channel in self.channels_to_join:
71
            log('Joining channel {} ...', channel.name)
72
            conn.join(channel.name, channel.password or '')
73
74 1
    def on_nicknameinuse(self, conn, event) -> None:
75
        """Choose another nickname if conflicting."""
76
        self._nickname += '_'
77
        conn.nick(self._nickname)
78
79 1
    def on_join(self, conn, event) -> None:
80
        """Successfully joined channel."""
81
        joined_nick = event.source.nick
82
        channel_name = event.target
83
84
        if joined_nick == self._nickname:
85
            log('Joined IRC channel: {}', channel_name)
86
            channel_joined.send(channel_name=channel_name)
87
88 1
    def on_badchannelkey(self, conn, event) -> None:
89
        """Channel could not be joined due to wrong password."""
90
        channel = event.arguments[0]
91
        log('Cannot join channel {} (bad key).', channel)
92
93 1
    def say(
94
        self,
95
        sender: Optional[Any],
96
        *,
97
        channel_name: Optional[str] = None,
98
        text: Optional[str] = None,
99
    ) -> None:
100
        """Say message on channel."""
101
        self.connection.privmsg(channel_name, text)
102
103
104 1
class DummyBot:
105 1
    def __init__(self, channels: List[IrcChannel]) -> None:
106
        self.channels = channels
107
108 1
    def start(self) -> None:
109
        # Fake channel joins.
110
        for channel in self.channels:
111
            channel_joined.send(channel_name=channel.name)
112
113 1
    def say(
114
        self, sender: Optional[Any], *, channel_name=None, text=None
115
    ) -> None:
116
        log('{}> {}', channel_name, text)
117
118 1
    def disconnect(self, msg: str) -> None:
119
        pass
120
121
122 1
def create_bot(config: IrcConfig) -> Union[Bot, DummyBot]:
123
    """Create and return an IRC bot according to the configuration."""
124
    if config.server is None:
125
        log('No IRC server specified; will write to STDOUT instead.')
126
        return DummyBot(config.channels)
127
128
    return Bot(config.server, config.nickname, config.realname, config.channels)
129