|
| 1 | +Introduction to asyncirc |
| 2 | +======================== |
| 3 | + |
| 4 | +Asyncirc is an IRC library based on Python's asyncio. It is designed to be easy |
| 5 | +to use, to provide a nice way of processing IRC events, and to provide a Good |
| 6 | +Enough(TM) abstraction of the IRC protocol. It is *not* designed to be a ready- |
| 7 | +made bot framework, or to be used without some knowledge of how IRC works. |
| 8 | + |
| 9 | +Installation |
| 10 | +------------ |
| 11 | +You can install asyncirc from PyPI, using whatever method you prefer. The |
| 12 | +package name is ``asyncio-irc``. |
| 13 | + |
| 14 | +Signals |
| 15 | +------- |
| 16 | +Instead of using callback-style event handler registration, asyncirc uses |
| 17 | +*signals*. Signals are provided by the excellent |
| 18 | +`Blinker<https://pythonhosted.org/blinker/>`_ library. It would be advisable |
| 19 | +to read that page for more information on signals, but the tl;dr is:: |
| 20 | + |
| 21 | + # register a signal handler |
| 22 | + signal("signal-name").connect(signal_handler_function) |
| 23 | + |
| 24 | + # send a signal |
| 25 | + signal("signal-name").send(sender, some="keyword", argu="ments") |
| 26 | + |
| 27 | +Asyncirc defines a lot of signals, which are covered in detail below. |
| 28 | + |
| 29 | +Usage |
| 30 | +===== |
| 31 | + |
| 32 | +Actually using asyncirc is pretty simple. First you need to import it:: |
| 33 | + |
| 34 | + from asyncirc import irc |
| 35 | + |
| 36 | +Then you need to create a connection:: |
| 37 | + |
| 38 | + conn = irc.connect("chat.freenode.net", 6697, use_ssl=True) |
| 39 | + |
| 40 | +You'll need to register with the server:: |
| 41 | + |
| 42 | + conn.register("nickname", "ident", "realname (can contain spaces)") |
| 43 | + |
| 44 | +Once you're registered and connected, you'll want to join some channels:: |
| 45 | + |
| 46 | + @conn.on("irc-001") |
| 47 | + def autojoin_channels(): |
| 48 | + conn.join(["#channel1", "#channel2"]) |
| 49 | + |
| 50 | +Maybe you want to connect some event handlers:: |
| 51 | + |
| 52 | + @conn.on("join") |
| 53 | + def on_join(message, user, channel): |
| 54 | + conn.say(channel, "Hi {}! You're connecting from {}.".format(user.nick, user.host)) |
| 55 | + |
| 56 | +Once you're done with that, you need to run the event loop:: |
| 57 | + |
| 58 | + import asyncio |
| 59 | + asyncio.get_event_loop().run_forever() |
| 60 | + |
| 61 | +Your shiny new IRC client should now connect and do what you told it to! |
| 62 | +Congratulations! |
| 63 | + |
| 64 | +Using plugins |
| 65 | +------------- |
| 66 | +Plugins let you do new stuff with your connection. To use them, you import them |
| 67 | +before you initially make the connection:: |
| 68 | + |
| 69 | + from asyncirc import irc |
| 70 | + import asyncirc.plugins.addressed |
| 71 | + |
| 72 | + conn = irc.connect(...) |
| 73 | + ... |
| 74 | + |
| 75 | +Plugins usually send new signals, so you want to handle those:: |
| 76 | + |
| 77 | + @conn.on("addressed") |
| 78 | + def on_addressed(message, user, target, text): |
| 79 | + # triggers on "bot_nickname: " or similar |
| 80 | + bot.say(target, "{}: You said {} to me!".format(user.nick, text)) |
| 81 | + |
| 82 | +Fundamental types |
| 83 | +================= |
| 84 | + |
| 85 | +There are a few arguments to your handlers that are instances of specific |
| 86 | +classes. Here are those: |
| 87 | + |
| 88 | +``user`` is usually an instance of the ``User`` class, which has some important |
| 89 | +attributes: |
| 90 | + |
| 91 | + ``User.nick`` contains the nickname of the user |
| 92 | + |
| 93 | + ``User.user`` contains the ident of the user |
| 94 | + |
| 95 | + ``User.host`` contains the host of the user |
| 96 | + |
| 97 | + ``User.hostmask`` contains the full hostmask of the user |
| 98 | + |
| 99 | +Events you can handle |
| 100 | +===================== |
| 101 | + |
| 102 | +There are a lot of things that can happen on IRC. As such, there are a lot of |
| 103 | +signals that asyncirc generates. Here's a list of some useful ones, with event |
| 104 | +handler signatures:: |
| 105 | + |
| 106 | + @conn.on("private-message") |
| 107 | + def on_private_message(message, user, target, text): |
| 108 | + ... |
| 109 | + |
| 110 | + @conn.on("public-message") |
| 111 | + def on_public_message(message, user, target, text): |
| 112 | + ... |
| 113 | + |
| 114 | + @conn.on("message") |
| 115 | + def on_any_message(message, user, target, text): |
| 116 | + ... |
| 117 | + |
| 118 | + @conn.on("private-notice") |
| 119 | + def on_private_notice(message, user, target, text): |
| 120 | + ... |
| 121 | + |
| 122 | + @conn.on("public-notice") |
| 123 | + def on_public_notice(message, user, target, text): |
| 124 | + ... |
| 125 | + |
| 126 | + @conn.on("notice") |
| 127 | + def on_any_notice(message, user, target, text): |
| 128 | + ... |
| 129 | + |
| 130 | + @conn.on("join") |
| 131 | + def on_join(message, user, channel): |
| 132 | + ... |
| 133 | + |
| 134 | + @conn.on("part") |
| 135 | + def on_join(message, user, channel, reason): |
| 136 | + # reason defaults to None if there is no reason |
| 137 | + ... |
| 138 | + |
| 139 | + @conn.on("quit") |
| 140 | + def on_quit(message, user, reason): |
| 141 | + ... |
| 142 | + |
| 143 | + @conn.on("kick") |
| 144 | + def on_kick(message, kicker, kickee, channel, reason): |
| 145 | + # kicker is a User object |
| 146 | + # kickee is just a nickname |
| 147 | + ... |
| 148 | + |
| 149 | + @conn.on("nick") |
| 150 | + def on_nick_change(message, user, new_nick): |
| 151 | + ... |
| 152 | + |
| 153 | +These signals are actually sent by the ``core`` plugin, so that's pretty neat. |
| 154 | + |
| 155 | +Just what is that ``message`` handler argument, anyway? |
| 156 | +------------------------------------------------------- |
| 157 | + |
| 158 | +``message`` is a special argument. It contains the parsed commands from the IRC |
| 159 | +server. It has a few useful attributes: |
| 160 | + |
| 161 | + ``message.params`` has the arguments of the command |
| 162 | + |
| 163 | + ``message.verb`` has the actual IRC verb |
| 164 | + |
| 165 | + ``message.sender`` has the hostmask of the sender |
| 166 | + |
| 167 | +``message`` is especially useful when you want to take care of events that don't |
| 168 | +already have a signal attached to them. You can hook into the ``irc`` event, or |
| 169 | +the ``irc-verb`` event to handle specific verbs. Handlers for that will take a |
| 170 | +single argument ``message``. |
| 171 | + |
| 172 | +Plugins |
| 173 | +======= |
| 174 | + |
| 175 | +There are a few plugins packaged with asyncirc. These are documented here. |
| 176 | + |
| 177 | +``asyncirc.plugins.nickserv`` |
| 178 | +----------------------------- |
| 179 | +Sends events when authentication to NickServ succeeds or fails. Automatically |
| 180 | +tries to regain your nickname when it is not available (usually doesn't work |
| 181 | +unless you've authenticated with SASL). |
| 182 | + |
| 183 | +Events:: |
| 184 | + |
| 185 | + @conn.on("nickserv-auth-success") |
| 186 | + def auth_success(message_text): |
| 187 | + # yay! you're authed to nickserv now. |
| 188 | + ... |
| 189 | + |
| 190 | + @conn.on("nickserv-auth-fail") |
| 191 | + def auth_fail(message_text): |
| 192 | + # oh no, you had the wrong password! |
| 193 | + # try again or exit! |
| 194 | + ... |
| 195 | + |
| 196 | +``asyncirc.plugins.sasl`` |
| 197 | +------------------------- |
| 198 | +Handles IRCv3 SASL authentication. After importing, there's a single method call |
| 199 | +you need to worry about:: |
| 200 | + |
| 201 | + asyncirc.plugins.sasl.auth(account_name, password) |
| 202 | + |
| 203 | +And a single event:: |
| 204 | + |
| 205 | + @conn.on("sasl-auth-complete") |
| 206 | + def sasl_auth_complete(message): |
| 207 | + # yay, you've authenticated with SASL. |
| 208 | + ... |
| 209 | + |
| 210 | +You probably don't even have to worry about the event. This plugin talks to the |
| 211 | +core plugin so that registration is delayed until SASL authentication is done. |
| 212 | + |
| 213 | +``asyncirc.plugins.cap`` |
| 214 | +------------------------ |
| 215 | +Handles IRCv3 capability negotiation. There's only one method you need to call |
| 216 | +to request a capability once you've imported this plugin:: |
| 217 | + |
| 218 | + asyncirc.plugins.cap.request_capability("extended-join") # or whatever |
| 219 | + |
| 220 | +The ``caps-acknowledged`` event will be fired when the server has acknowledged |
| 221 | +our request for capabilities. As soon as we know what set of capabilities the |
| 222 | +server supports, the ``caps-known`` event is fired. |
| 223 | + |
| 224 | +``asyncirc.plugins.tracking`` |
| 225 | +----------------------------- |
| 226 | +Full state tracking. Some methods:: |
| 227 | + |
| 228 | + user = asyncirc.plugins.tracking.get_user(hostmask_or_nick) |
| 229 | + chan = asyncirc.plugins.tracking.get_channel(channel_name) |
| 230 | + |
| 231 | +Based on that, here's some stuff you can do:: |
| 232 | + |
| 233 | + chan.users # a list of nicknames in the channel |
| 234 | + user.channels # a list of channels that the user is in |
| 235 | + user.account # the user's services account name. works best if you've |
| 236 | + # requested the extended-join and account-notify capabilities |
| 237 | + chan.mode # return the channel's mode string |
| 238 | + user.previous_nicks # return the user's previous nicknames that we know of |
| 239 | + |
| 240 | +How it actually works is really complicated. Don't even ask. |
| 241 | + |
| 242 | +``asyncirc.plugins.addressed`` |
| 243 | +------------------------------ |
| 244 | +It has an event that fires when someone mentions your bot by name in IRC:: |
| 245 | + |
| 246 | + @conn.on("addressed") |
| 247 | + def on_me_addressed(message, user, target, text): |
| 248 | + # text contains the text without the "your_bot: " part |
| 249 | + ... |
| 250 | + |
| 251 | +You can also register command characters that can be used instead of your bot's |
| 252 | +nickname:: |
| 253 | + |
| 254 | + asyncirc.plugins.addressed.register_command_character(";;") |
| 255 | + |
| 256 | +Questions? Issues? Just want to chat? |
| 257 | +===================================== |
| 258 | + |
| 259 | +I'm fwilson on freenode, if you have any questions. I hang out in |
| 260 | +``#watchtower`` along with the rest of the Watchtower dev team. Feel free to |
| 261 | +join us! |
0 commit comments