├── COPYING ├── ChangeLog ├── Makefile ├── README ├── dccreceive ├── dccsend ├── ircbot.py ├── irccat ├── irccat2 ├── irclib.py ├── python-irclib.spec ├── servermap ├── setup.py └── testbot.py /COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU LESSER GENERAL PUBLIC LICENSE 3 | Version 2.1, February 1999 4 | 5 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | [This is the first released version of the Lesser GPL. It also counts 11 | as the successor of the GNU Library Public License, version 2, hence 12 | the version number 2.1.] 13 | 14 | Preamble 15 | 16 | The licenses for most software are designed to take away your 17 | freedom to share and change it. By contrast, the GNU General Public 18 | Licenses are intended to guarantee your freedom to share and change 19 | free software--to make sure the software is free for all its users. 20 | 21 | This license, the Lesser General Public License, applies to some 22 | specially designated software packages--typically libraries--of the 23 | Free Software Foundation and other authors who decide to use it. You 24 | can use it too, but we suggest you first think carefully about whether 25 | this license or the ordinary General Public License is the better 26 | strategy to use in any particular case, based on the explanations 27 | below. 28 | 29 | When we speak of free software, we are referring to freedom of use, 30 | not price. Our General Public Licenses are designed to make sure that 31 | you have the freedom to distribute copies of free software (and charge 32 | for this service if you wish); that you receive source code or can get 33 | it if you want it; that you can change the software and use pieces of 34 | it in new free programs; and that you are informed that you can do 35 | these things. 36 | 37 | To protect your rights, we need to make restrictions that forbid 38 | distributors to deny you these rights or to ask you to surrender these 39 | rights. These restrictions translate to certain responsibilities for 40 | you if you distribute copies of the library or if you modify it. 41 | 42 | For example, if you distribute copies of the library, whether gratis 43 | or for a fee, you must give the recipients all the rights that we gave 44 | you. You must make sure that they, too, receive or can get the source 45 | code. If you link other code with the library, you must provide 46 | complete object files to the recipients, so that they can relink them 47 | with the library after making changes to the library and recompiling 48 | it. And you must show them these terms so they know their rights. 49 | 50 | We protect your rights with a two-step method: (1) we copyright the 51 | library, and (2) we offer you this license, which gives you legal 52 | permission to copy, distribute and/or modify the library. 53 | 54 | To protect each distributor, we want to make it very clear that 55 | there is no warranty for the free library. Also, if the library is 56 | modified by someone else and passed on, the recipients should know 57 | that what they have is not the original version, so that the original 58 | author's reputation will not be affected by problems that might be 59 | introduced by others. 60 | ^L 61 | Finally, software patents pose a constant threat to the existence of 62 | any free program. We wish to make sure that a company cannot 63 | effectively restrict the users of a free program by obtaining a 64 | restrictive license from a patent holder. Therefore, we insist that 65 | any patent license obtained for a version of the library must be 66 | consistent with the full freedom of use specified in this license. 67 | 68 | Most GNU software, including some libraries, is covered by the 69 | ordinary GNU General Public License. This license, the GNU Lesser 70 | General Public License, applies to certain designated libraries, and 71 | is quite different from the ordinary General Public License. We use 72 | this license for certain libraries in order to permit linking those 73 | libraries into non-free programs. 74 | 75 | When a program is linked with a library, whether statically or using 76 | a shared library, the combination of the two is legally speaking a 77 | combined work, a derivative of the original library. The ordinary 78 | General Public License therefore permits such linking only if the 79 | entire combination fits its criteria of freedom. The Lesser General 80 | Public License permits more lax criteria for linking other code with 81 | the library. 82 | 83 | We call this license the "Lesser" General Public License because it 84 | does Less to protect the user's freedom than the ordinary General 85 | Public License. It also provides other free software developers Less 86 | of an advantage over competing non-free programs. These disadvantages 87 | are the reason we use the ordinary General Public License for many 88 | libraries. However, the Lesser license provides advantages in certain 89 | special circumstances. 90 | 91 | For example, on rare occasions, there may be a special need to 92 | encourage the widest possible use of a certain library, so that it 93 | becomes a de-facto standard. To achieve this, non-free programs must 94 | be allowed to use the library. A more frequent case is that a free 95 | library does the same job as widely used non-free libraries. In this 96 | case, there is little to gain by limiting the free library to free 97 | software only, so we use the Lesser General Public License. 98 | 99 | In other cases, permission to use a particular library in non-free 100 | programs enables a greater number of people to use a large body of 101 | free software. For example, permission to use the GNU C Library in 102 | non-free programs enables many more people to use the whole GNU 103 | operating system, as well as its variant, the GNU/Linux operating 104 | system. 105 | 106 | Although the Lesser General Public License is Less protective of the 107 | users' freedom, it does ensure that the user of a program that is 108 | linked with the Library has the freedom and the wherewithal to run 109 | that program using a modified version of the Library. 110 | 111 | The precise terms and conditions for copying, distribution and 112 | modification follow. Pay close attention to the difference between a 113 | "work based on the library" and a "work that uses the library". The 114 | former contains code derived from the library, whereas the latter must 115 | be combined with the library in order to run. 116 | ^L 117 | GNU LESSER GENERAL PUBLIC LICENSE 118 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 119 | 120 | 0. This License Agreement applies to any software library or other 121 | program which contains a notice placed by the copyright holder or 122 | other authorized party saying it may be distributed under the terms of 123 | this Lesser General Public License (also called "this License"). 124 | Each licensee is addressed as "you". 125 | 126 | A "library" means a collection of software functions and/or data 127 | prepared so as to be conveniently linked with application programs 128 | (which use some of those functions and data) to form executables. 129 | 130 | The "Library", below, refers to any such software library or work 131 | which has been distributed under these terms. A "work based on the 132 | Library" means either the Library or any derivative work under 133 | copyright law: that is to say, a work containing the Library or a 134 | portion of it, either verbatim or with modifications and/or translated 135 | straightforwardly into another language. (Hereinafter, translation is 136 | included without limitation in the term "modification".) 137 | 138 | "Source code" for a work means the preferred form of the work for 139 | making modifications to it. For a library, complete source code means 140 | all the source code for all modules it contains, plus any associated 141 | interface definition files, plus the scripts used to control 142 | compilation and installation of the library. 143 | 144 | Activities other than copying, distribution and modification are not 145 | covered by this License; they are outside its scope. The act of 146 | running a program using the Library is not restricted, and output from 147 | such a program is covered only if its contents constitute a work based 148 | on the Library (independent of the use of the Library in a tool for 149 | writing it). Whether that is true depends on what the Library does 150 | and what the program that uses the Library does. 151 | 152 | 1. You may copy and distribute verbatim copies of the Library's 153 | complete source code as you receive it, in any medium, provided that 154 | you conspicuously and appropriately publish on each copy an 155 | appropriate copyright notice and disclaimer of warranty; keep intact 156 | all the notices that refer to this License and to the absence of any 157 | warranty; and distribute a copy of this License along with the 158 | Library. 159 | 160 | You may charge a fee for the physical act of transferring a copy, 161 | and you may at your option offer warranty protection in exchange for a 162 | fee. 163 | 164 | 2. You may modify your copy or copies of the Library or any portion 165 | of it, thus forming a work based on the Library, and copy and 166 | distribute such modifications or work under the terms of Section 1 167 | above, provided that you also meet all of these conditions: 168 | 169 | a) The modified work must itself be a software library. 170 | 171 | b) You must cause the files modified to carry prominent notices 172 | stating that you changed the files and the date of any change. 173 | 174 | c) You must cause the whole of the work to be licensed at no 175 | charge to all third parties under the terms of this License. 176 | 177 | d) If a facility in the modified Library refers to a function or a 178 | table of data to be supplied by an application program that uses 179 | the facility, other than as an argument passed when the facility 180 | is invoked, then you must make a good faith effort to ensure that, 181 | in the event an application does not supply such function or 182 | table, the facility still operates, and performs whatever part of 183 | its purpose remains meaningful. 184 | 185 | (For example, a function in a library to compute square roots has 186 | a purpose that is entirely well-defined independent of the 187 | application. Therefore, Subsection 2d requires that any 188 | application-supplied function or table used by this function must 189 | be optional: if the application does not supply it, the square 190 | root function must still compute square roots.) 191 | 192 | These requirements apply to the modified work as a whole. If 193 | identifiable sections of that work are not derived from the Library, 194 | and can be reasonably considered independent and separate works in 195 | themselves, then this License, and its terms, do not apply to those 196 | sections when you distribute them as separate works. But when you 197 | distribute the same sections as part of a whole which is a work based 198 | on the Library, the distribution of the whole must be on the terms of 199 | this License, whose permissions for other licensees extend to the 200 | entire whole, and thus to each and every part regardless of who wrote 201 | it. 202 | 203 | Thus, it is not the intent of this section to claim rights or contest 204 | your rights to work written entirely by you; rather, the intent is to 205 | exercise the right to control the distribution of derivative or 206 | collective works based on the Library. 207 | 208 | In addition, mere aggregation of another work not based on the Library 209 | with the Library (or with a work based on the Library) on a volume of 210 | a storage or distribution medium does not bring the other work under 211 | the scope of this License. 212 | 213 | 3. You may opt to apply the terms of the ordinary GNU General Public 214 | License instead of this License to a given copy of the Library. To do 215 | this, you must alter all the notices that refer to this License, so 216 | that they refer to the ordinary GNU General Public License, version 2, 217 | instead of to this License. (If a newer version than version 2 of the 218 | ordinary GNU General Public License has appeared, then you can specify 219 | that version instead if you wish.) Do not make any other change in 220 | these notices. 221 | ^L 222 | Once this change is made in a given copy, it is irreversible for 223 | that copy, so the ordinary GNU General Public License applies to all 224 | subsequent copies and derivative works made from that copy. 225 | 226 | This option is useful when you wish to copy part of the code of 227 | the Library into a program that is not a library. 228 | 229 | 4. You may copy and distribute the Library (or a portion or 230 | derivative of it, under Section 2) in object code or executable form 231 | under the terms of Sections 1 and 2 above provided that you accompany 232 | it with the complete corresponding machine-readable source code, which 233 | must be distributed under the terms of Sections 1 and 2 above on a 234 | medium customarily used for software interchange. 235 | 236 | If distribution of object code is made by offering access to copy 237 | from a designated place, then offering equivalent access to copy the 238 | source code from the same place satisfies the requirement to 239 | distribute the source code, even though third parties are not 240 | compelled to copy the source along with the object code. 241 | 242 | 5. A program that contains no derivative of any portion of the 243 | Library, but is designed to work with the Library by being compiled or 244 | linked with it, is called a "work that uses the Library". Such a 245 | work, in isolation, is not a derivative work of the Library, and 246 | therefore falls outside the scope of this License. 247 | 248 | However, linking a "work that uses the Library" with the Library 249 | creates an executable that is a derivative of the Library (because it 250 | contains portions of the Library), rather than a "work that uses the 251 | library". The executable is therefore covered by this License. 252 | Section 6 states terms for distribution of such executables. 253 | 254 | When a "work that uses the Library" uses material from a header file 255 | that is part of the Library, the object code for the work may be a 256 | derivative work of the Library even though the source code is not. 257 | Whether this is true is especially significant if the work can be 258 | linked without the Library, or if the work is itself a library. The 259 | threshold for this to be true is not precisely defined by law. 260 | 261 | If such an object file uses only numerical parameters, data 262 | structure layouts and accessors, and small macros and small inline 263 | functions (ten lines or less in length), then the use of the object 264 | file is unrestricted, regardless of whether it is legally a derivative 265 | work. (Executables containing this object code plus portions of the 266 | Library will still fall under Section 6.) 267 | 268 | Otherwise, if the work is a derivative of the Library, you may 269 | distribute the object code for the work under the terms of Section 6. 270 | Any executables containing that work also fall under Section 6, 271 | whether or not they are linked directly with the Library itself. 272 | ^L 273 | 6. As an exception to the Sections above, you may also combine or 274 | link a "work that uses the Library" with the Library to produce a 275 | work containing portions of the Library, and distribute that work 276 | under terms of your choice, provided that the terms permit 277 | modification of the work for the customer's own use and reverse 278 | engineering for debugging such modifications. 279 | 280 | You must give prominent notice with each copy of the work that the 281 | Library is used in it and that the Library and its use are covered by 282 | this License. You must supply a copy of this License. If the work 283 | during execution displays copyright notices, you must include the 284 | copyright notice for the Library among them, as well as a reference 285 | directing the user to the copy of this License. Also, you must do one 286 | of these things: 287 | 288 | a) Accompany the work with the complete corresponding 289 | machine-readable source code for the Library including whatever 290 | changes were used in the work (which must be distributed under 291 | Sections 1 and 2 above); and, if the work is an executable linked 292 | with the Library, with the complete machine-readable "work that 293 | uses the Library", as object code and/or source code, so that the 294 | user can modify the Library and then relink to produce a modified 295 | executable containing the modified Library. (It is understood 296 | that the user who changes the contents of definitions files in the 297 | Library will not necessarily be able to recompile the application 298 | to use the modified definitions.) 299 | 300 | b) Use a suitable shared library mechanism for linking with the 301 | Library. A suitable mechanism is one that (1) uses at run time a 302 | copy of the library already present on the user's computer system, 303 | rather than copying library functions into the executable, and (2) 304 | will operate properly with a modified version of the library, if 305 | the user installs one, as long as the modified version is 306 | interface-compatible with the version that the work was made with. 307 | 308 | c) Accompany the work with a written offer, valid for at least 309 | three years, to give the same user the materials specified in 310 | Subsection 6a, above, for a charge no more than the cost of 311 | performing this distribution. 312 | 313 | d) If distribution of the work is made by offering access to copy 314 | from a designated place, offer equivalent access to copy the above 315 | specified materials from the same place. 316 | 317 | e) Verify that the user has already received a copy of these 318 | materials or that you have already sent this user a copy. 319 | 320 | For an executable, the required form of the "work that uses the 321 | Library" must include any data and utility programs needed for 322 | reproducing the executable from it. However, as a special exception, 323 | the materials to be distributed need not include anything that is 324 | normally distributed (in either source or binary form) with the major 325 | components (compiler, kernel, and so on) of the operating system on 326 | which the executable runs, unless that component itself accompanies 327 | the executable. 328 | 329 | It may happen that this requirement contradicts the license 330 | restrictions of other proprietary libraries that do not normally 331 | accompany the operating system. Such a contradiction means you cannot 332 | use both them and the Library together in an executable that you 333 | distribute. 334 | ^L 335 | 7. You may place library facilities that are a work based on the 336 | Library side-by-side in a single library together with other library 337 | facilities not covered by this License, and distribute such a combined 338 | library, provided that the separate distribution of the work based on 339 | the Library and of the other library facilities is otherwise 340 | permitted, and provided that you do these two things: 341 | 342 | a) Accompany the combined library with a copy of the same work 343 | based on the Library, uncombined with any other library 344 | facilities. This must be distributed under the terms of the 345 | Sections above. 346 | 347 | b) Give prominent notice with the combined library of the fact 348 | that part of it is a work based on the Library, and explaining 349 | where to find the accompanying uncombined form of the same work. 350 | 351 | 8. You may not copy, modify, sublicense, link with, or distribute 352 | the Library except as expressly provided under this License. Any 353 | attempt otherwise to copy, modify, sublicense, link with, or 354 | distribute the Library is void, and will automatically terminate your 355 | rights under this License. However, parties who have received copies, 356 | or rights, from you under this License will not have their licenses 357 | terminated so long as such parties remain in full compliance. 358 | 359 | 9. You are not required to accept this License, since you have not 360 | signed it. However, nothing else grants you permission to modify or 361 | distribute the Library or its derivative works. These actions are 362 | prohibited by law if you do not accept this License. Therefore, by 363 | modifying or distributing the Library (or any work based on the 364 | Library), you indicate your acceptance of this License to do so, and 365 | all its terms and conditions for copying, distributing or modifying 366 | the Library or works based on it. 367 | 368 | 10. Each time you redistribute the Library (or any work based on the 369 | Library), the recipient automatically receives a license from the 370 | original licensor to copy, distribute, link with or modify the Library 371 | subject to these terms and conditions. You may not impose any further 372 | restrictions on the recipients' exercise of the rights granted herein. 373 | You are not responsible for enforcing compliance by third parties with 374 | this License. 375 | ^L 376 | 11. If, as a consequence of a court judgment or allegation of patent 377 | infringement or for any other reason (not limited to patent issues), 378 | conditions are imposed on you (whether by court order, agreement or 379 | otherwise) that contradict the conditions of this License, they do not 380 | excuse you from the conditions of this License. If you cannot 381 | distribute so as to satisfy simultaneously your obligations under this 382 | License and any other pertinent obligations, then as a consequence you 383 | may not distribute the Library at all. For example, if a patent 384 | license would not permit royalty-free redistribution of the Library by 385 | all those who receive copies directly or indirectly through you, then 386 | the only way you could satisfy both it and this License would be to 387 | refrain entirely from distribution of the Library. 388 | 389 | If any portion of this section is held invalid or unenforceable under 390 | any particular circumstance, the balance of the section is intended to 391 | apply, and the section as a whole is intended to apply in other 392 | circumstances. 393 | 394 | It is not the purpose of this section to induce you to infringe any 395 | patents or other property right claims or to contest validity of any 396 | such claims; this section has the sole purpose of protecting the 397 | integrity of the free software distribution system which is 398 | implemented by public license practices. Many people have made 399 | generous contributions to the wide range of software distributed 400 | through that system in reliance on consistent application of that 401 | system; it is up to the author/donor to decide if he or she is willing 402 | to distribute software through any other system and a licensee cannot 403 | impose that choice. 404 | 405 | This section is intended to make thoroughly clear what is believed to 406 | be a consequence of the rest of this License. 407 | 408 | 12. If the distribution and/or use of the Library is restricted in 409 | certain countries either by patents or by copyrighted interfaces, the 410 | original copyright holder who places the Library under this License 411 | may add an explicit geographical distribution limitation excluding those 412 | countries, so that distribution is permitted only in or among 413 | countries not thus excluded. In such case, this License incorporates 414 | the limitation as if written in the body of this License. 415 | 416 | 13. The Free Software Foundation may publish revised and/or new 417 | versions of the Lesser General Public License from time to time. 418 | Such new versions will be similar in spirit to the present version, 419 | but may differ in detail to address new problems or concerns. 420 | 421 | Each version is given a distinguishing version number. If the Library 422 | specifies a version number of this License which applies to it and 423 | "any later version", you have the option of following the terms and 424 | conditions either of that version or of any later version published by 425 | the Free Software Foundation. If the Library does not specify a 426 | license version number, you may choose any version ever published by 427 | the Free Software Foundation. 428 | ^L 429 | 14. If you wish to incorporate parts of the Library into other free 430 | programs whose distribution conditions are incompatible with these, 431 | write to the author to ask for permission. For software which is 432 | copyrighted by the Free Software Foundation, write to the Free 433 | Software Foundation; we sometimes make exceptions for this. Our 434 | decision will be guided by the two goals of preserving the free status 435 | of all derivatives of our free software and of promoting the sharing 436 | and reuse of software generally. 437 | 438 | NO WARRANTY 439 | 440 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 441 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 442 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 443 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 444 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 445 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 446 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 447 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 448 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 449 | 450 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 451 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 452 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 453 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 454 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 455 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 456 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 457 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 458 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 459 | DAMAGES. 460 | 461 | END OF TERMS AND CONDITIONS 462 | ^L 463 | How to Apply These Terms to Your New Libraries 464 | 465 | If you develop a new library, and you want it to be of the greatest 466 | possible use to the public, we recommend making it free software that 467 | everyone can redistribute and change. You can do so by permitting 468 | redistribution under these terms (or, alternatively, under the terms 469 | of the ordinary General Public License). 470 | 471 | To apply these terms, attach the following notices to the library. 472 | It is safest to attach them to the start of each source file to most 473 | effectively convey the exclusion of warranty; and each file should 474 | have at least the "copyright" line and a pointer to where the full 475 | notice is found. 476 | 477 | 478 | 479 | Copyright (C) 480 | 481 | This library is free software; you can redistribute it and/or 482 | modify it under the terms of the GNU Lesser General Public 483 | License as published by the Free Software Foundation; either 484 | version 2.1 of the License, or (at your option) any later version. 485 | 486 | This library is distributed in the hope that it will be useful, 487 | but WITHOUT ANY WARRANTY; without even the implied warranty of 488 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 489 | Lesser General Public License for more details. 490 | 491 | You should have received a copy of the GNU Lesser General Public 492 | License along with this library; if not, write to the Free Software 493 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 494 | 495 | Also add information on how to contact you by electronic and paper mail. 496 | 497 | You should also get your employer (if you work as a programmer) or 498 | your school, if any, to sign a "copyright disclaimer" for the library, 499 | if necessary. Here is a sample; alter the names: 500 | 501 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 502 | library `Frob' (a library for tweaking knobs) written by James 503 | Random Hacker. 504 | 505 | , 1 April 1990 506 | Ty Coon, President of Vice 507 | 508 | That's all there is to it! 509 | 510 | 511 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbalogh/python-irclib/4c7ede54efafa94329e07981e4f848e8dbe5980e/ChangeLog -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := `sed -n -e '/VERSION = /{s/VERSION = \(.*\), \(.*\), \(.*\)/\1.\2.\3/;p;}' setup.py 25 | 26 | python-irclib.spec: python-irclib.spec.in 27 | sed 's/%%VERSION%%/'$(VERSION)'/g' python-irclib.spec.in >python-irclib.spec 28 | 29 | dist: $(DISTFILES) 30 | mkdir $(PACKAGENAME) 31 | cp -r $(DISTFILES) $(PACKAGENAME) 32 | tar cvzf $(PACKAGENAME).tar.gz $(PACKAGENAME) 33 | zip -r9yq $(PACKAGENAME).zip $(PACKAGENAME) 34 | rm -rf $(PACKAGENAME) 35 | 36 | cvstag: 37 | ver=$(VERSION); echo cvs tag version_`echo $$ver | sed 's/\./_/g'` 38 | 39 | clean: 40 | rm -rf *~ *.pyc build python-irclib.spec setup.py 41 | 42 | .PHONY: all doc dist cvstag clean 43 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | irclib -- Internet Relay Chat (IRC) protocol client library 2 | ----------------------------------------------------------- 3 | 4 | The home of irclib.py is now: 5 | 6 | http://python-irclib.sourceforge.net 7 | 8 | This library is intended to encapsulate the IRC protocol at a quite 9 | low level. It provides an event-driven IRC client framework. It has 10 | a fairly thorough support for the basic IRC protocol, CTCP and DCC 11 | connections. 12 | 13 | In order to understand how to make an IRC client, I'm afraid you more 14 | or less must understand the IRC specifications. They are available 15 | here: 16 | 17 | http://www.irchelp.org/irchelp/rfc/ 18 | 19 | Requirements: 20 | 21 | * Python 2.2 or newer. 22 | 23 | Installation: 24 | 25 | * Run "python setup.py install" or copy irclib.py and/or ircbot.py 26 | to an appropriate Python module directory. 27 | 28 | The main features of the IRC client framework are: 29 | 30 | * Abstraction of the IRC protocol. 31 | * Handles multiple simultaneous IRC server connections. 32 | * Handles server PONGing transparently. 33 | * Messages to the IRC server are done by calling methods on an IRC 34 | connection object. 35 | * Messages from an IRC server triggers events, which can be caught 36 | by event handlers. 37 | * Reading from and writing to IRC server sockets are normally done 38 | by an internal select() loop, but the select()ing may be done by 39 | an external main loop. 40 | * Functions can be registered to execute at specified times by the 41 | event-loop. 42 | * Decodes CTCP tagging correctly (hopefully); I haven't seen any 43 | other IRC client implementation that handles the CTCP 44 | specification subtilties. 45 | * A kind of simple, single-server, object-oriented IRC client class 46 | that dispatches events to instance methods is included. 47 | * DCC connection support. 48 | 49 | Current limitations: 50 | 51 | * The IRC protocol shines through the abstraction a bit too much. 52 | * Data is not written asynchronously to the server (and DCC peers), 53 | i.e. the write() may block if the TCP buffers are stuffed. 54 | * Like most projects, documentation is lacking... 55 | 56 | Unfortunately, this library isn't as well-documented as I would like 57 | it to be. I think the best way to get started is to read and 58 | understand the example program irccat, which is included in the 59 | distribution. 60 | 61 | The following files might be of interest: 62 | 63 | * irclib.py 64 | 65 | The library itself. Read the code along with comments and 66 | docstrings to get a grip of what it does. Use it at your own risk 67 | and read the source, Luke! 68 | 69 | * irccat 70 | 71 | A simple example of how to use irclib.py. irccat reads text from 72 | stdin and writes it to a specified user or channel on an IRC 73 | server. 74 | 75 | * irccat2 76 | 77 | The same as above, but using the SimpleIRCClient class. 78 | 79 | * servermap 80 | 81 | Another simple example. servermap connects to an IRC server, 82 | finds out what other IRC servers there are in the net and prints 83 | a tree-like map of their interconnections. 84 | 85 | * testbot.py 86 | 87 | An example bot that uses the SingleServerIRCBot class from 88 | ircbot.py. The bot enters a channel and listens for commands in 89 | private messages or channel traffic. It also accepts DCC 90 | invitations and echos back sent DCC chat messages. 91 | 92 | * dccreceive 93 | 94 | Receives a file over DCC. 95 | 96 | * dccsend 97 | 98 | Sends a file over DCC. 99 | 100 | 101 | NOTE: If you're running one of the examples on a unix command line, you need to escape the # symbol in the channel. For example, use \#test instead of #test. 102 | 103 | 104 | Enjoy. 105 | 106 | Maintainer: 107 | keltus 108 | 109 | Original Author: 110 | Joel Rosdahl 111 | -------------------------------------------------------------------------------- /dccreceive: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Example program using irclib.py. 4 | # 5 | # This program is free without restrictions; do anything you like with 6 | # it. 7 | # 8 | # Joel Rosdahl 9 | 10 | import irclib 11 | import os 12 | import struct 13 | import sys 14 | 15 | class DCCReceive(irclib.SimpleIRCClient): 16 | def __init__(self): 17 | irclib.SimpleIRCClient.__init__(self) 18 | self.received_bytes = 0 19 | 20 | def on_ctcp(self, connection, event): 21 | args = event.arguments()[1].split() 22 | if args[0] != "SEND": 23 | return 24 | self.filename = os.path.basename(args[1]) 25 | if os.path.exists(self.filename): 26 | print "A file named", self.filename, 27 | print "already exists. Refusing to save it." 28 | self.connection.quit() 29 | self.file = open(self.filename, "w") 30 | peeraddress = irclib.ip_numstr_to_quad(args[2]) 31 | peerport = int(args[3]) 32 | self.dcc = self.dcc_connect(peeraddress, peerport, "raw") 33 | 34 | def on_dccmsg(self, connection, event): 35 | data = event.arguments()[0] 36 | self.file.write(data) 37 | self.received_bytes = self.received_bytes + len(data) 38 | self.dcc.privmsg(struct.pack("!I", self.received_bytes)) 39 | 40 | def on_dcc_disconnect(self, connection, event): 41 | self.file.close() 42 | print "Received file %s (%d bytes)." % (self.filename, 43 | self.received_bytes) 44 | self.connection.quit() 45 | 46 | def on_disconnect(self, connection, event): 47 | sys.exit(0) 48 | 49 | def main(): 50 | if len(sys.argv) != 3: 51 | print "Usage: dccreceive " 52 | print "\nReceives one file via DCC and then exits. The file is stored in the" 53 | print "current directory." 54 | sys.exit(1) 55 | 56 | s = sys.argv[1].split(":", 1) 57 | server = s[0] 58 | if len(s) == 2: 59 | try: 60 | port = int(s[1]) 61 | except ValueError: 62 | print "Error: Erroneous port." 63 | sys.exit(1) 64 | else: 65 | port = 6667 66 | nickname = sys.argv[2] 67 | 68 | c = DCCReceive() 69 | try: 70 | c.connect(server, port, nickname) 71 | except irclib.ServerConnectionError, x: 72 | print x 73 | sys.exit(1) 74 | c.start() 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /dccsend: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Example program using irclib.py. 4 | # 5 | # This program is free without restrictions; do anything you like with 6 | # it. 7 | # 8 | # Joel Rosdahl 9 | 10 | import irclib 11 | import os 12 | import struct 13 | import sys 14 | 15 | class DCCSend(irclib.SimpleIRCClient): 16 | def __init__(self, receiver, filename): 17 | irclib.SimpleIRCClient.__init__(self) 18 | self.receiver = receiver 19 | self.filename = filename 20 | self.filesize = os.path.getsize(self.filename) 21 | self.file = open(filename) 22 | self.sent_bytes = 0 23 | 24 | def on_welcome(self, connection, event): 25 | self.dcc = self.dcc_listen("raw") 26 | self.connection.ctcp("DCC", self.receiver, "SEND %s %s %d %d" % ( 27 | os.path.basename(self.filename), 28 | irclib.ip_quad_to_numstr(self.dcc.localaddress), 29 | self.dcc.localport, 30 | self.filesize)) 31 | 32 | def on_dcc_connect(self, connection, event): 33 | if self.filesize == 0: 34 | self.dcc.disconnect() 35 | return 36 | self.send_chunk() 37 | 38 | def on_dcc_disconnect(self, connection, event): 39 | print "Sent file %s (%d bytes)." % (self.filename, self.filesize) 40 | self.connection.quit() 41 | 42 | def on_dccmsg(self, connection, event): 43 | acked = struct.unpack("!I", event.arguments()[0])[0] 44 | if acked == self.filesize: 45 | self.dcc.disconnect() 46 | self.connection.quit() 47 | elif acked == self.sent_bytes: 48 | self.send_chunk() 49 | 50 | def on_disconnect(self, connection, event): 51 | sys.exit(0) 52 | 53 | def on_nosuchnick(self, connection, event): 54 | print "No such nickname:", event.arguments()[0] 55 | self.connection.quit() 56 | 57 | def send_chunk(self): 58 | data = self.file.read(1024) 59 | self.dcc.privmsg(data) 60 | self.sent_bytes = self.sent_bytes + len(data) 61 | 62 | def main(): 63 | if len(sys.argv) != 5: 64 | print "Usage: dccsend " 65 | print "\nSends to via DCC and then exits." 66 | sys.exit(1) 67 | 68 | s = sys.argv[1].split(":", 1) 69 | server = s[0] 70 | if len(s) == 2: 71 | try: 72 | port = int(s[1]) 73 | except ValueError: 74 | print "Error: Erroneous port." 75 | sys.exit(1) 76 | else: 77 | port = 6667 78 | nickname = sys.argv[2] 79 | receiver = sys.argv[3] 80 | filename = sys.argv[4] 81 | 82 | c = DCCSend(receiver, filename) 83 | try: 84 | c.connect(server, port, nickname) 85 | except irclib.ServerConnectionError, x: 86 | print x 87 | sys.exit(1) 88 | c.start() 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /ircbot.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 1999--2002 Joel Rosdahl 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 | # 17 | # Joel Rosdahl 18 | # 19 | # $Id: ircbot.py,v 1.23 2008/09/11 07:38:30 keltus Exp $ 20 | 21 | """ircbot -- Simple IRC bot library. 22 | 23 | This module contains a single-server IRC bot class that can be used to 24 | write simpler bots. 25 | """ 26 | 27 | import sys 28 | from UserDict import UserDict 29 | 30 | from irclib import SimpleIRCClient 31 | from irclib import nm_to_n, irc_lower, all_events 32 | from irclib import parse_channel_modes, is_channel 33 | from irclib import ServerConnectionError 34 | 35 | class SingleServerIRCBot(SimpleIRCClient): 36 | """A single-server IRC bot class. 37 | 38 | The bot tries to reconnect if it is disconnected. 39 | 40 | The bot keeps track of the channels it has joined, the other 41 | clients that are present in the channels and which of those that 42 | have operator or voice modes. The "database" is kept in the 43 | self.channels attribute, which is an IRCDict of Channels. 44 | """ 45 | def __init__(self, server_list, nickname, realname, reconnection_interval=60): 46 | """Constructor for SingleServerIRCBot objects. 47 | 48 | Arguments: 49 | 50 | server_list -- A list of tuples (server, port) that 51 | defines which servers the bot should try to 52 | connect to. 53 | 54 | nickname -- The bot's nickname. 55 | 56 | realname -- The bot's realname. 57 | 58 | reconnection_interval -- How long the bot should wait 59 | before trying to reconnect. 60 | 61 | dcc_connections -- A list of initiated/accepted DCC 62 | connections. 63 | """ 64 | 65 | SimpleIRCClient.__init__(self) 66 | self.channels = IRCDict() 67 | self.server_list = server_list 68 | if not reconnection_interval or reconnection_interval < 0: 69 | reconnection_interval = 2**31 70 | self.reconnection_interval = reconnection_interval 71 | 72 | self._nickname = nickname 73 | self._realname = realname 74 | for i in ["disconnect", "join", "kick", "mode", 75 | "namreply", "nick", "part", "quit"]: 76 | self.connection.add_global_handler(i, 77 | getattr(self, "_on_" + i), 78 | -10) 79 | def _connected_checker(self): 80 | """[Internal]""" 81 | if not self.connection.is_connected(): 82 | self.connection.execute_delayed(self.reconnection_interval, 83 | self._connected_checker) 84 | self.jump_server() 85 | 86 | def _connect(self): 87 | """[Internal]""" 88 | password = None 89 | if len(self.server_list[0]) > 2: 90 | password = self.server_list[0][2] 91 | try: 92 | self.connect(self.server_list[0][0], 93 | self.server_list[0][1], 94 | self._nickname, 95 | password, 96 | ircname=self._realname) 97 | except ServerConnectionError: 98 | pass 99 | 100 | def _on_disconnect(self, c, e): 101 | """[Internal]""" 102 | self.channels = IRCDict() 103 | self.connection.execute_delayed(self.reconnection_interval, 104 | self._connected_checker) 105 | 106 | def _on_join(self, c, e): 107 | """[Internal]""" 108 | ch = e.target() 109 | nick = nm_to_n(e.source()) 110 | if nick == c.get_nickname(): 111 | self.channels[ch] = Channel() 112 | self.channels[ch].add_user(nick) 113 | 114 | def _on_kick(self, c, e): 115 | """[Internal]""" 116 | nick = e.arguments()[0] 117 | channel = e.target() 118 | 119 | if nick == c.get_nickname(): 120 | del self.channels[channel] 121 | else: 122 | self.channels[channel].remove_user(nick) 123 | 124 | def _on_mode(self, c, e): 125 | """[Internal]""" 126 | modes = parse_channel_modes(" ".join(e.arguments())) 127 | t = e.target() 128 | if is_channel(t): 129 | ch = self.channels[t] 130 | for mode in modes: 131 | if mode[0] == "+": 132 | f = ch.set_mode 133 | else: 134 | f = ch.clear_mode 135 | f(mode[1], mode[2]) 136 | else: 137 | # Mode on self... XXX 138 | pass 139 | 140 | def _on_namreply(self, c, e): 141 | """[Internal]""" 142 | 143 | # e.arguments()[0] == "@" for secret channels, 144 | # "*" for private channels, 145 | # "=" for others (public channels) 146 | # e.arguments()[1] == channel 147 | # e.arguments()[2] == nick list 148 | 149 | ch = e.arguments()[1] 150 | for nick in e.arguments()[2].split(): 151 | if nick[0] == "@": 152 | nick = nick[1:] 153 | self.channels[ch].set_mode("o", nick) 154 | elif nick[0] == "+": 155 | nick = nick[1:] 156 | self.channels[ch].set_mode("v", nick) 157 | self.channels[ch].add_user(nick) 158 | 159 | def _on_nick(self, c, e): 160 | """[Internal]""" 161 | before = nm_to_n(e.source()) 162 | after = e.target() 163 | for ch in self.channels.values(): 164 | if ch.has_user(before): 165 | ch.change_nick(before, after) 166 | 167 | def _on_part(self, c, e): 168 | """[Internal]""" 169 | nick = nm_to_n(e.source()) 170 | channel = e.target() 171 | 172 | if nick == c.get_nickname(): 173 | del self.channels[channel] 174 | else: 175 | self.channels[channel].remove_user(nick) 176 | 177 | def _on_quit(self, c, e): 178 | """[Internal]""" 179 | nick = nm_to_n(e.source()) 180 | for ch in self.channels.values(): 181 | if ch.has_user(nick): 182 | ch.remove_user(nick) 183 | 184 | def die(self, msg="Bye, cruel world!"): 185 | """Let the bot die. 186 | 187 | Arguments: 188 | 189 | msg -- Quit message. 190 | """ 191 | 192 | self.connection.disconnect(msg) 193 | sys.exit(0) 194 | 195 | def disconnect(self, msg="I'll be back!"): 196 | """Disconnect the bot. 197 | 198 | The bot will try to reconnect after a while. 199 | 200 | Arguments: 201 | 202 | msg -- Quit message. 203 | """ 204 | self.connection.disconnect(msg) 205 | 206 | def get_version(self): 207 | """Returns the bot version. 208 | 209 | Used when answering a CTCP VERSION request. 210 | """ 211 | return "ircbot.py by Joel Rosdahl " 212 | 213 | def jump_server(self, msg="Changing servers"): 214 | """Connect to a new server, possibly disconnecting from the current. 215 | 216 | The bot will skip to next server in the server_list each time 217 | jump_server is called. 218 | """ 219 | if self.connection.is_connected(): 220 | self.connection.disconnect(msg) 221 | 222 | self.server_list.append(self.server_list.pop(0)) 223 | self._connect() 224 | 225 | def on_ctcp(self, c, e): 226 | """Default handler for ctcp events. 227 | 228 | Replies to VERSION and PING requests and relays DCC requests 229 | to the on_dccchat method. 230 | """ 231 | if e.arguments()[0] == "VERSION": 232 | c.ctcp_reply(nm_to_n(e.source()), 233 | "VERSION " + self.get_version()) 234 | elif e.arguments()[0] == "PING": 235 | if len(e.arguments()) > 1: 236 | c.ctcp_reply(nm_to_n(e.source()), 237 | "PING " + e.arguments()[1]) 238 | elif e.arguments()[0] == "DCC" and e.arguments()[1].split(" ", 1)[0] == "CHAT": 239 | self.on_dccchat(c, e) 240 | 241 | def on_dccchat(self, c, e): 242 | pass 243 | 244 | def start(self): 245 | """Start the bot.""" 246 | self._connect() 247 | SimpleIRCClient.start(self) 248 | 249 | 250 | class IRCDict: 251 | """A dictionary suitable for storing IRC-related things. 252 | 253 | Dictionary keys a and b are considered equal if and only if 254 | irc_lower(a) == irc_lower(b) 255 | 256 | Otherwise, it should behave exactly as a normal dictionary. 257 | """ 258 | 259 | def __init__(self, dict=None): 260 | self.data = {} 261 | self.canon_keys = {} # Canonical keys 262 | if dict is not None: 263 | self.update(dict) 264 | def __repr__(self): 265 | return repr(self.data) 266 | def __cmp__(self, dict): 267 | if isinstance(dict, IRCDict): 268 | return cmp(self.data, dict.data) 269 | else: 270 | return cmp(self.data, dict) 271 | def __len__(self): 272 | return len(self.data) 273 | def __getitem__(self, key): 274 | return self.data[self.canon_keys[irc_lower(key)]] 275 | def __setitem__(self, key, item): 276 | if key in self: 277 | del self[key] 278 | self.data[key] = item 279 | self.canon_keys[irc_lower(key)] = key 280 | def __delitem__(self, key): 281 | ck = irc_lower(key) 282 | del self.data[self.canon_keys[ck]] 283 | del self.canon_keys[ck] 284 | def __iter__(self): 285 | return iter(self.data) 286 | def __contains__(self, key): 287 | return self.has_key(key) 288 | def clear(self): 289 | self.data.clear() 290 | self.canon_keys.clear() 291 | def copy(self): 292 | if self.__class__ is UserDict: 293 | return UserDict(self.data) 294 | import copy 295 | return copy.copy(self) 296 | def keys(self): 297 | return self.data.keys() 298 | def items(self): 299 | return self.data.items() 300 | def values(self): 301 | return self.data.values() 302 | def has_key(self, key): 303 | return irc_lower(key) in self.canon_keys 304 | def update(self, dict): 305 | for k, v in dict.items(): 306 | self.data[k] = v 307 | def get(self, key, failobj=None): 308 | return self.data.get(key, failobj) 309 | 310 | 311 | class Channel: 312 | """A class for keeping information about an IRC channel. 313 | 314 | This class can be improved a lot. 315 | """ 316 | 317 | def __init__(self): 318 | self.userdict = IRCDict() 319 | self.operdict = IRCDict() 320 | self.voiceddict = IRCDict() 321 | self.modes = {} 322 | 323 | def users(self): 324 | """Returns an unsorted list of the channel's users.""" 325 | return self.userdict.keys() 326 | 327 | def opers(self): 328 | """Returns an unsorted list of the channel's operators.""" 329 | return self.operdict.keys() 330 | 331 | def voiced(self): 332 | """Returns an unsorted list of the persons that have voice 333 | mode set in the channel.""" 334 | return self.voiceddict.keys() 335 | 336 | def has_user(self, nick): 337 | """Check whether the channel has a user.""" 338 | return nick in self.userdict 339 | 340 | def is_oper(self, nick): 341 | """Check whether a user has operator status in the channel.""" 342 | return nick in self.operdict 343 | 344 | def is_voiced(self, nick): 345 | """Check whether a user has voice mode set in the channel.""" 346 | return nick in self.voiceddict 347 | 348 | def add_user(self, nick): 349 | self.userdict[nick] = 1 350 | 351 | def remove_user(self, nick): 352 | for d in self.userdict, self.operdict, self.voiceddict: 353 | if nick in d: 354 | del d[nick] 355 | 356 | def change_nick(self, before, after): 357 | self.userdict[after] = 1 358 | del self.userdict[before] 359 | if before in self.operdict: 360 | self.operdict[after] = 1 361 | del self.operdict[before] 362 | if before in self.voiceddict: 363 | self.voiceddict[after] = 1 364 | del self.voiceddict[before] 365 | 366 | def set_mode(self, mode, value=None): 367 | """Set mode on the channel. 368 | 369 | Arguments: 370 | 371 | mode -- The mode (a single-character string). 372 | 373 | value -- Value 374 | """ 375 | if mode == "o": 376 | self.operdict[value] = 1 377 | elif mode == "v": 378 | self.voiceddict[value] = 1 379 | else: 380 | self.modes[mode] = value 381 | 382 | def clear_mode(self, mode, value=None): 383 | """Clear mode on the channel. 384 | 385 | Arguments: 386 | 387 | mode -- The mode (a single-character string). 388 | 389 | value -- Value 390 | """ 391 | try: 392 | if mode == "o": 393 | del self.operdict[value] 394 | elif mode == "v": 395 | del self.voiceddict[value] 396 | else: 397 | del self.modes[mode] 398 | except KeyError: 399 | pass 400 | 401 | def has_mode(self, mode): 402 | return mode in self.modes 403 | 404 | def is_moderated(self): 405 | return self.has_mode("m") 406 | 407 | def is_secret(self): 408 | return self.has_mode("s") 409 | 410 | def is_protected(self): 411 | return self.has_mode("p") 412 | 413 | def has_topic_lock(self): 414 | return self.has_mode("t") 415 | 416 | def is_invite_only(self): 417 | return self.has_mode("i") 418 | 419 | def has_allow_external_messages(self): 420 | return self.has_mode("n") 421 | 422 | def has_limit(self): 423 | return self.has_mode("l") 424 | 425 | def limit(self): 426 | if self.has_limit(): 427 | return self.modes[l] 428 | else: 429 | return None 430 | 431 | def has_key(self): 432 | return self.has_mode("k") 433 | 434 | def key(self): 435 | if self.has_key(): 436 | return self.modes["k"] 437 | else: 438 | return None 439 | -------------------------------------------------------------------------------- /irccat: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Example program using irclib.py. 4 | # 5 | # This program is free without restrictions; do anything you like with 6 | # it. 7 | # 8 | # Joel Rosdahl 9 | 10 | import irclib 11 | import sys 12 | 13 | def on_connect(connection, event): 14 | if irclib.is_channel(target): 15 | connection.join(target) 16 | else: 17 | while 1: 18 | line = sys.stdin.readline() 19 | if not line: 20 | break 21 | connection.privmsg(target, line) 22 | connection.quit("Using irclib.py") 23 | 24 | def on_join(connection, event): 25 | while 1: 26 | line = sys.stdin.readline() 27 | if not line: 28 | break 29 | connection.privmsg(target, line) 30 | connection.quit("Using irclib.py") 31 | 32 | if len(sys.argv) != 4: 33 | print "Usage: irccat " 34 | print "\ntarget is a nickname or a channel." 35 | sys.exit(1) 36 | 37 | def on_disconnect(connection, event): 38 | sys.exit(0) 39 | 40 | s = sys.argv[1].split(":", 1) 41 | server = s[0] 42 | if len(s) == 2: 43 | try: 44 | port = int(s[1]) 45 | except ValueError: 46 | print "Error: Erroneous port." 47 | sys.exit(1) 48 | else: 49 | port = 6667 50 | nickname = sys.argv[2] 51 | target = sys.argv[3] 52 | 53 | irc = irclib.IRC() 54 | try: 55 | c = irc.server().connect(server, port, nickname) 56 | except irclib.ServerConnectionError, x: 57 | print x 58 | sys.exit(1) 59 | 60 | c.add_global_handler("welcome", on_connect) 61 | c.add_global_handler("join", on_join) 62 | c.add_global_handler("disconnect", on_disconnect) 63 | 64 | irc.process_forever() 65 | -------------------------------------------------------------------------------- /irccat2: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Example program using irclib.py. 4 | # 5 | # This program is free without restrictions; do anything you like with 6 | # it. 7 | # 8 | # Joel Rosdahl 9 | 10 | import irclib 11 | import sys 12 | 13 | class IRCCat(irclib.SimpleIRCClient): 14 | def __init__(self, target): 15 | irclib.SimpleIRCClient.__init__(self) 16 | self.target = target 17 | 18 | def on_welcome(self, connection, event): 19 | if irclib.is_channel(self.target): 20 | connection.join(self.target) 21 | else: 22 | self.send_it() 23 | 24 | def on_join(self, connection, event): 25 | self.send_it() 26 | 27 | def on_disconnect(self, connection, event): 28 | sys.exit(0) 29 | 30 | def send_it(self): 31 | while 1: 32 | line = sys.stdin.readline() 33 | if not line: 34 | break 35 | self.connection.privmsg(self.target, line) 36 | self.connection.quit("Using irclib.py") 37 | 38 | def main(): 39 | if len(sys.argv) != 4: 40 | print "Usage: irccat2 " 41 | print "\ntarget is a nickname or a channel." 42 | sys.exit(1) 43 | 44 | s = sys.argv[1].split(":", 1) 45 | server = s[0] 46 | if len(s) == 2: 47 | try: 48 | port = int(s[1]) 49 | except ValueError: 50 | print "Error: Erroneous port." 51 | sys.exit(1) 52 | else: 53 | port = 6667 54 | nickname = sys.argv[2] 55 | target = sys.argv[3] 56 | 57 | c = IRCCat(target) 58 | try: 59 | c.connect(server, port, nickname) 60 | except irclib.ServerConnectionError, x: 61 | print x 62 | sys.exit(1) 63 | c.start() 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /irclib.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 1999--2002 Joel Rosdahl 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 | # 17 | # keltus 18 | # 19 | # $Id: irclib.py,v 1.47 2008/09/25 22:00:59 keltus Exp $ 20 | 21 | """irclib -- Internet Relay Chat (IRC) protocol client library. 22 | 23 | This library is intended to encapsulate the IRC protocol at a quite 24 | low level. It provides an event-driven IRC client framework. It has 25 | a fairly thorough support for the basic IRC protocol, CTCP, DCC chat, 26 | but DCC file transfers is not yet supported. 27 | 28 | In order to understand how to make an IRC client, I'm afraid you more 29 | or less must understand the IRC specifications. They are available 30 | here: [IRC specifications]. 31 | 32 | The main features of the IRC client framework are: 33 | 34 | * Abstraction of the IRC protocol. 35 | * Handles multiple simultaneous IRC server connections. 36 | * Handles server PONGing transparently. 37 | * Messages to the IRC server are done by calling methods on an IRC 38 | connection object. 39 | * Messages from an IRC server triggers events, which can be caught 40 | by event handlers. 41 | * Reading from and writing to IRC server sockets are normally done 42 | by an internal select() loop, but the select()ing may be done by 43 | an external main loop. 44 | * Functions can be registered to execute at specified times by the 45 | event-loop. 46 | * Decodes CTCP tagging correctly (hopefully); I haven't seen any 47 | other IRC client implementation that handles the CTCP 48 | specification subtilties. 49 | * A kind of simple, single-server, object-oriented IRC client class 50 | that dispatches events to instance methods is included. 51 | 52 | Current limitations: 53 | 54 | * The IRC protocol shines through the abstraction a bit too much. 55 | * Data is not written asynchronously to the server, i.e. the write() 56 | may block if the TCP buffers are stuffed. 57 | * There are no support for DCC file transfers. 58 | * The author haven't even read RFC 2810, 2811, 2812 and 2813. 59 | * Like most projects, documentation is lacking... 60 | 61 | .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/ 62 | """ 63 | 64 | import bisect 65 | import re 66 | import select 67 | import socket 68 | import string 69 | import sys 70 | import time 71 | import types 72 | 73 | VERSION = 0, 4, 8 74 | DEBUG = 0 75 | 76 | # TODO 77 | # ---- 78 | # (maybe) thread safety 79 | # (maybe) color parser convenience functions 80 | # documentation (including all event types) 81 | # (maybe) add awareness of different types of ircds 82 | # send data asynchronously to the server (and DCC connections) 83 | # (maybe) automatically close unused, passive DCC connections after a while 84 | 85 | # NOTES 86 | # ----- 87 | # connection.quit() only sends QUIT to the server. 88 | # ERROR from the server triggers the error event and the disconnect event. 89 | # dropping of the connection triggers the disconnect event. 90 | 91 | class IRCError(Exception): 92 | """Represents an IRC exception.""" 93 | pass 94 | 95 | 96 | class IRC: 97 | """Class that handles one or several IRC server connections. 98 | 99 | When an IRC object has been instantiated, it can be used to create 100 | Connection objects that represent the IRC connections. The 101 | responsibility of the IRC object is to provide an event-driven 102 | framework for the connections and to keep the connections alive. 103 | It runs a select loop to poll each connection's TCP socket and 104 | hands over the sockets with incoming data for processing by the 105 | corresponding connection. 106 | 107 | The methods of most interest for an IRC client writer are server, 108 | add_global_handler, remove_global_handler, execute_at, 109 | execute_delayed, process_once and process_forever. 110 | 111 | Here is an example: 112 | 113 | irc = irclib.IRC() 114 | server = irc.server() 115 | server.connect(\"irc.some.where\", 6667, \"my_nickname\") 116 | server.privmsg(\"a_nickname\", \"Hi there!\") 117 | irc.process_forever() 118 | 119 | This will connect to the IRC server irc.some.where on port 6667 120 | using the nickname my_nickname and send the message \"Hi there!\" 121 | to the nickname a_nickname. 122 | """ 123 | 124 | def __init__(self, fn_to_add_socket=None, 125 | fn_to_remove_socket=None, 126 | fn_to_add_timeout=None): 127 | """Constructor for IRC objects. 128 | 129 | Optional arguments are fn_to_add_socket, fn_to_remove_socket 130 | and fn_to_add_timeout. The first two specify functions that 131 | will be called with a socket object as argument when the IRC 132 | object wants to be notified (or stop being notified) of data 133 | coming on a new socket. When new data arrives, the method 134 | process_data should be called. Similarly, fn_to_add_timeout 135 | is called with a number of seconds (a floating point number) 136 | as first argument when the IRC object wants to receive a 137 | notification (by calling the process_timeout method). So, if 138 | e.g. the argument is 42.17, the object wants the 139 | process_timeout method to be called after 42 seconds and 170 140 | milliseconds. 141 | 142 | The three arguments mainly exist to be able to use an external 143 | main loop (for example Tkinter's or PyGTK's main app loop) 144 | instead of calling the process_forever method. 145 | 146 | An alternative is to just call ServerConnection.process_once() 147 | once in a while. 148 | """ 149 | 150 | if fn_to_add_socket and fn_to_remove_socket: 151 | self.fn_to_add_socket = fn_to_add_socket 152 | self.fn_to_remove_socket = fn_to_remove_socket 153 | else: 154 | self.fn_to_add_socket = None 155 | self.fn_to_remove_socket = None 156 | 157 | self.fn_to_add_timeout = fn_to_add_timeout 158 | self.connections = [] 159 | self.handlers = {} 160 | self.delayed_commands = [] # list of tuples in the format (time, function, arguments) 161 | 162 | self.add_global_handler("ping", _ping_ponger, -42) 163 | 164 | def server(self): 165 | """Creates and returns a ServerConnection object.""" 166 | 167 | c = ServerConnection(self) 168 | self.connections.append(c) 169 | return c 170 | 171 | def process_data(self, sockets): 172 | """Called when there is more data to read on connection sockets. 173 | 174 | Arguments: 175 | 176 | sockets -- A list of socket objects. 177 | 178 | See documentation for IRC.__init__. 179 | """ 180 | for s in sockets: 181 | for c in self.connections: 182 | if s == c._get_socket(): 183 | c.process_data() 184 | 185 | def process_timeout(self): 186 | """Called when a timeout notification is due. 187 | 188 | See documentation for IRC.__init__. 189 | """ 190 | t = time.time() 191 | while self.delayed_commands: 192 | if t >= self.delayed_commands[0][0]: 193 | self.delayed_commands[0][1](*self.delayed_commands[0][2]) 194 | del self.delayed_commands[0] 195 | else: 196 | break 197 | 198 | def process_once(self, timeout=0): 199 | """Process data from connections once. 200 | 201 | Arguments: 202 | 203 | timeout -- How long the select() call should wait if no 204 | data is available. 205 | 206 | This method should be called periodically to check and process 207 | incoming data, if there are any. If that seems boring, look 208 | at the process_forever method. 209 | """ 210 | sockets = map(lambda x: x._get_socket(), self.connections) 211 | sockets = filter(lambda x: x != None, sockets) 212 | if sockets: 213 | (i, o, e) = select.select(sockets, [], [], timeout) 214 | self.process_data(i) 215 | else: 216 | time.sleep(timeout) 217 | self.process_timeout() 218 | 219 | def process_forever(self, timeout=0.2): 220 | """Run an infinite loop, processing data from connections. 221 | 222 | This method repeatedly calls process_once. 223 | 224 | Arguments: 225 | 226 | timeout -- Parameter to pass to process_once. 227 | """ 228 | while 1: 229 | self.process_once(timeout) 230 | 231 | def disconnect_all(self, message=""): 232 | """Disconnects all connections.""" 233 | for c in self.connections: 234 | c.disconnect(message) 235 | 236 | def add_global_handler(self, event, handler, priority=0): 237 | """Adds a global handler function for a specific event type. 238 | 239 | Arguments: 240 | 241 | event -- Event type (a string). Check the values of the 242 | numeric_events dictionary in irclib.py for possible event 243 | types. 244 | 245 | handler -- Callback function. 246 | 247 | priority -- A number (the lower number, the higher priority). 248 | 249 | The handler function is called whenever the specified event is 250 | triggered in any of the connections. See documentation for 251 | the Event class. 252 | 253 | The handler functions are called in priority order (lowest 254 | number is highest priority). If a handler function returns 255 | \"NO MORE\", no more handlers will be called. 256 | """ 257 | if not event in self.handlers: 258 | self.handlers[event] = [] 259 | bisect.insort(self.handlers[event], ((priority, handler))) 260 | 261 | def remove_global_handler(self, event, handler): 262 | """Removes a global handler function. 263 | 264 | Arguments: 265 | 266 | event -- Event type (a string). 267 | 268 | handler -- Callback function. 269 | 270 | Returns 1 on success, otherwise 0. 271 | """ 272 | if not event in self.handlers: 273 | return 0 274 | for h in self.handlers[event]: 275 | if handler == h[1]: 276 | self.handlers[event].remove(h) 277 | return 1 278 | 279 | def execute_at(self, at, function, arguments=()): 280 | """Execute a function at a specified time. 281 | 282 | Arguments: 283 | 284 | at -- Execute at this time (standard \"time_t\" time). 285 | 286 | function -- Function to call. 287 | 288 | arguments -- Arguments to give the function. 289 | """ 290 | self.execute_delayed(at-time.time(), function, arguments) 291 | 292 | def execute_delayed(self, delay, function, arguments=()): 293 | """Execute a function after a specified time. 294 | 295 | Arguments: 296 | 297 | delay -- How many seconds to wait. 298 | 299 | function -- Function to call. 300 | 301 | arguments -- Arguments to give the function. 302 | """ 303 | bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments)) 304 | if self.fn_to_add_timeout: 305 | self.fn_to_add_timeout(delay) 306 | 307 | def dcc(self, dcctype="chat"): 308 | """Creates and returns a DCCConnection object. 309 | 310 | Arguments: 311 | 312 | dcctype -- "chat" for DCC CHAT connections or "raw" for 313 | DCC SEND (or other DCC types). If "chat", 314 | incoming data will be split in newline-separated 315 | chunks. If "raw", incoming data is not touched. 316 | """ 317 | c = DCCConnection(self, dcctype) 318 | self.connections.append(c) 319 | return c 320 | 321 | def _handle_event(self, connection, event): 322 | """[Internal]""" 323 | h = self.handlers 324 | for handler in h.get("all_events", []) + h.get(event.eventtype(), []): 325 | if handler[1](connection, event) == "NO MORE": 326 | return 327 | 328 | def _remove_connection(self, connection): 329 | """[Internal]""" 330 | self.connections.remove(connection) 331 | if self.fn_to_remove_socket: 332 | self.fn_to_remove_socket(connection._get_socket()) 333 | 334 | _rfc_1459_command_regexp = re.compile("^(:(?P[^ ]+) +)?(?P[^ ]+)( *(?P .+))?") 335 | 336 | class Connection: 337 | """Base class for IRC connections. 338 | 339 | Must be overridden. 340 | """ 341 | def __init__(self, irclibobj): 342 | self.irclibobj = irclibobj 343 | 344 | def _get_socket(): 345 | raise IRCError, "Not overridden" 346 | 347 | ############################## 348 | ### Convenience wrappers. 349 | 350 | def execute_at(self, at, function, arguments=()): 351 | self.irclibobj.execute_at(at, function, arguments) 352 | 353 | def execute_delayed(self, delay, function, arguments=()): 354 | self.irclibobj.execute_delayed(delay, function, arguments) 355 | 356 | 357 | class ServerConnectionError(IRCError): 358 | pass 359 | 360 | class ServerNotConnectedError(ServerConnectionError): 361 | pass 362 | 363 | 364 | # Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to 365 | # use \n as message separator! :P 366 | _linesep_regexp = re.compile("\r?\n") 367 | 368 | class ServerConnection(Connection): 369 | """This class represents an IRC server connection. 370 | 371 | ServerConnection objects are instantiated by calling the server 372 | method on an IRC object. 373 | """ 374 | 375 | def __init__(self, irclibobj): 376 | Connection.__init__(self, irclibobj) 377 | self.connected = 0 # Not connected yet. 378 | self.socket = None 379 | self.ssl = None 380 | 381 | def connect(self, server, port, nickname, password=None, username=None, 382 | ircname=None, localaddress="", localport=0, ssl=False, ipv6=False): 383 | """Connect/reconnect to a server. 384 | 385 | Arguments: 386 | 387 | server -- Server name. 388 | 389 | port -- Port number. 390 | 391 | nickname -- The nickname. 392 | 393 | password -- Password (if any). 394 | 395 | username -- The username. 396 | 397 | ircname -- The IRC name ("realname"). 398 | 399 | localaddress -- Bind the connection to a specific local IP address. 400 | 401 | localport -- Bind the connection to a specific local port. 402 | 403 | ssl -- Enable support for ssl. 404 | 405 | ipv6 -- Enable support for ipv6. 406 | 407 | This function can be called to reconnect a closed connection. 408 | 409 | Returns the ServerConnection object. 410 | """ 411 | if self.connected: 412 | self.disconnect("Changing servers") 413 | 414 | self.previous_buffer = "" 415 | self.handlers = {} 416 | self.real_server_name = "" 417 | self.real_nickname = nickname 418 | self.server = server 419 | self.port = port 420 | self.nickname = nickname 421 | self.username = username or nickname 422 | self.ircname = ircname or nickname 423 | self.password = password 424 | self.localaddress = localaddress 425 | self.localport = localport 426 | self.localhost = socket.gethostname() 427 | if ipv6: 428 | self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 429 | else: 430 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 431 | try: 432 | self.socket.bind((self.localaddress, self.localport)) 433 | self.socket.connect((self.server, self.port)) 434 | if ssl: 435 | self.ssl = socket.ssl(self.socket) 436 | except socket.error, x: 437 | self.socket.close() 438 | self.socket = None 439 | raise ServerConnectionError, "Couldn't connect to socket: %s" % x 440 | self.connected = 1 441 | if self.irclibobj.fn_to_add_socket: 442 | self.irclibobj.fn_to_add_socket(self.socket) 443 | 444 | # Log on... 445 | if self.password: 446 | self.pass_(self.password) 447 | self.nick(self.nickname) 448 | self.user(self.username, self.ircname) 449 | return self 450 | 451 | def close(self): 452 | """Close the connection. 453 | 454 | This method closes the connection permanently; after it has 455 | been called, the object is unusable. 456 | """ 457 | 458 | self.disconnect("Closing object") 459 | self.irclibobj._remove_connection(self) 460 | 461 | def _get_socket(self): 462 | """[Internal]""" 463 | return self.socket 464 | 465 | def get_server_name(self): 466 | """Get the (real) server name. 467 | 468 | This method returns the (real) server name, or, more 469 | specifically, what the server calls itself. 470 | """ 471 | 472 | if self.real_server_name: 473 | return self.real_server_name 474 | else: 475 | return "" 476 | 477 | def get_nickname(self): 478 | """Get the (real) nick name. 479 | 480 | This method returns the (real) nickname. The library keeps 481 | track of nick changes, so it might not be the nick name that 482 | was passed to the connect() method. """ 483 | 484 | return self.real_nickname 485 | 486 | def process_data(self): 487 | """[Internal]""" 488 | 489 | try: 490 | if self.ssl: 491 | new_data = self.ssl.read(2**14) 492 | else: 493 | new_data = self.socket.recv(2**14) 494 | except socket.error, x: 495 | # The server hung up. 496 | self.disconnect("Connection reset by peer") 497 | return 498 | if not new_data: 499 | # Read nothing: connection must be down. 500 | self.disconnect("Connection reset by peer") 501 | return 502 | 503 | lines = _linesep_regexp.split(self.previous_buffer + new_data) 504 | 505 | # Save the last, unfinished line. 506 | self.previous_buffer = lines.pop() 507 | 508 | for line in lines: 509 | if DEBUG: 510 | print "FROM SERVER:", line 511 | 512 | if not line: 513 | continue 514 | 515 | prefix = None 516 | command = None 517 | arguments = None 518 | self._handle_event(Event("all_raw_messages", 519 | self.get_server_name(), 520 | None, 521 | [line])) 522 | 523 | m = _rfc_1459_command_regexp.match(line) 524 | if m.group("prefix"): 525 | prefix = m.group("prefix") 526 | if not self.real_server_name: 527 | self.real_server_name = prefix 528 | 529 | if m.group("command"): 530 | command = m.group("command").lower() 531 | 532 | if m.group("argument"): 533 | a = m.group("argument").split(" :", 1) 534 | arguments = a[0].split() 535 | if len(a) == 2: 536 | arguments.append(a[1]) 537 | 538 | # Translate numerics into more readable strings. 539 | if command in numeric_events: 540 | command = numeric_events[command] 541 | 542 | if command == "nick": 543 | if nm_to_n(prefix) == self.real_nickname: 544 | self.real_nickname = arguments[0] 545 | elif command == "welcome": 546 | # Record the nickname in case the client changed nick 547 | # in a nicknameinuse callback. 548 | self.real_nickname = arguments[0] 549 | 550 | if command in ["privmsg", "notice"]: 551 | target, message = arguments[0], arguments[1] 552 | messages = _ctcp_dequote(message) 553 | 554 | if command == "privmsg": 555 | if is_channel(target): 556 | command = "pubmsg" 557 | else: 558 | if is_channel(target): 559 | command = "pubnotice" 560 | else: 561 | command = "privnotice" 562 | 563 | for m in messages: 564 | if type(m) is types.TupleType: 565 | if command in ["privmsg", "pubmsg"]: 566 | command = "ctcp" 567 | else: 568 | command = "ctcpreply" 569 | 570 | m = list(m) 571 | if DEBUG: 572 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 573 | command, prefix, target, m) 574 | self._handle_event(Event(command, prefix, target, m)) 575 | if command == "ctcp" and m[0] == "ACTION": 576 | self._handle_event(Event("action", prefix, target, m[1:])) 577 | else: 578 | if DEBUG: 579 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 580 | command, prefix, target, [m]) 581 | self._handle_event(Event(command, prefix, target, [m])) 582 | else: 583 | target = None 584 | 585 | if command == "quit": 586 | arguments = [arguments[0]] 587 | elif command == "ping": 588 | target = arguments[0] 589 | else: 590 | target = arguments[0] 591 | arguments = arguments[1:] 592 | 593 | if command == "mode": 594 | if not is_channel(target): 595 | command = "umode" 596 | 597 | if DEBUG: 598 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 599 | command, prefix, target, arguments) 600 | self._handle_event(Event(command, prefix, target, arguments)) 601 | 602 | def _handle_event(self, event): 603 | """[Internal]""" 604 | self.irclibobj._handle_event(self, event) 605 | if event.eventtype() in self.handlers: 606 | for fn in self.handlers[event.eventtype()]: 607 | fn(self, event) 608 | 609 | def is_connected(self): 610 | """Return connection status. 611 | 612 | Returns true if connected, otherwise false. 613 | """ 614 | return self.connected 615 | 616 | def add_global_handler(self, *args): 617 | """Add global handler. 618 | 619 | See documentation for IRC.add_global_handler. 620 | """ 621 | self.irclibobj.add_global_handler(*args) 622 | 623 | def remove_global_handler(self, *args): 624 | """Remove global handler. 625 | 626 | See documentation for IRC.remove_global_handler. 627 | """ 628 | self.irclibobj.remove_global_handler(*args) 629 | 630 | def action(self, target, action): 631 | """Send a CTCP ACTION command.""" 632 | self.ctcp("ACTION", target, action) 633 | 634 | def admin(self, server=""): 635 | """Send an ADMIN command.""" 636 | self.send_raw(" ".join(["ADMIN", server]).strip()) 637 | 638 | def ctcp(self, ctcptype, target, parameter=""): 639 | """Send a CTCP command.""" 640 | ctcptype = ctcptype.upper() 641 | self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or "")) 642 | 643 | def ctcp_reply(self, target, parameter): 644 | """Send a CTCP REPLY command.""" 645 | self.notice(target, "\001%s\001" % parameter) 646 | 647 | def disconnect(self, message=""): 648 | """Hang up the connection. 649 | 650 | Arguments: 651 | 652 | message -- Quit message. 653 | """ 654 | if not self.connected: 655 | return 656 | 657 | self.connected = 0 658 | 659 | self.quit(message) 660 | 661 | try: 662 | self.socket.close() 663 | except socket.error, x: 664 | pass 665 | self.socket = None 666 | self._handle_event(Event("disconnect", self.server, "", [message])) 667 | 668 | def globops(self, text): 669 | """Send a GLOBOPS command.""" 670 | self.send_raw("GLOBOPS :" + text) 671 | 672 | def info(self, server=""): 673 | """Send an INFO command.""" 674 | self.send_raw(" ".join(["INFO", server]).strip()) 675 | 676 | def invite(self, nick, channel): 677 | """Send an INVITE command.""" 678 | self.send_raw(" ".join(["INVITE", nick, channel]).strip()) 679 | 680 | def ison(self, nicks): 681 | """Send an ISON command. 682 | 683 | Arguments: 684 | 685 | nicks -- List of nicks. 686 | """ 687 | self.send_raw("ISON " + " ".join(nicks)) 688 | 689 | def join(self, channel, key=""): 690 | """Send a JOIN command.""" 691 | self.send_raw("JOIN %s%s" % (channel, (key and (" " + key)))) 692 | 693 | def kick(self, channel, nick, comment=""): 694 | """Send a KICK command.""" 695 | self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment)))) 696 | 697 | def links(self, remote_server="", server_mask=""): 698 | """Send a LINKS command.""" 699 | command = "LINKS" 700 | if remote_server: 701 | command = command + " " + remote_server 702 | if server_mask: 703 | command = command + " " + server_mask 704 | self.send_raw(command) 705 | 706 | def list(self, channels=None, server=""): 707 | """Send a LIST command.""" 708 | command = "LIST" 709 | if channels: 710 | command = command + " " + ",".join(channels) 711 | if server: 712 | command = command + " " + server 713 | self.send_raw(command) 714 | 715 | def lusers(self, server=""): 716 | """Send a LUSERS command.""" 717 | self.send_raw("LUSERS" + (server and (" " + server))) 718 | 719 | def mode(self, target, command): 720 | """Send a MODE command.""" 721 | self.send_raw("MODE %s %s" % (target, command)) 722 | 723 | def motd(self, server=""): 724 | """Send an MOTD command.""" 725 | self.send_raw("MOTD" + (server and (" " + server))) 726 | 727 | def names(self, channels=None): 728 | """Send a NAMES command.""" 729 | self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or "")) 730 | 731 | def nick(self, newnick): 732 | """Send a NICK command.""" 733 | self.send_raw("NICK " + newnick) 734 | 735 | def notice(self, target, text): 736 | """Send a NOTICE command.""" 737 | # Should limit len(text) here! 738 | self.send_raw("NOTICE %s :%s" % (target, text)) 739 | 740 | def oper(self, nick, password): 741 | """Send an OPER command.""" 742 | self.send_raw("OPER %s %s" % (nick, password)) 743 | 744 | def part(self, channels, message=""): 745 | """Send a PART command.""" 746 | if type(channels) == types.StringType: 747 | self.send_raw("PART " + channels + (message and (" " + message))) 748 | else: 749 | self.send_raw("PART " + ",".join(channels) + (message and (" " + message))) 750 | 751 | def pass_(self, password): 752 | """Send a PASS command.""" 753 | self.send_raw("PASS " + password) 754 | 755 | def ping(self, target, target2=""): 756 | """Send a PING command.""" 757 | self.send_raw("PING %s%s" % (target, target2 and (" " + target2))) 758 | 759 | def pong(self, target, target2=""): 760 | """Send a PONG command.""" 761 | self.send_raw("PONG %s%s" % (target, target2 and (" " + target2))) 762 | 763 | def privmsg(self, target, text): 764 | """Send a PRIVMSG command.""" 765 | # Should limit len(text) here! 766 | self.send_raw("PRIVMSG %s :%s" % (target, text)) 767 | 768 | def privmsg_many(self, targets, text): 769 | """Send a PRIVMSG command to multiple targets.""" 770 | # Should limit len(text) here! 771 | self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text)) 772 | 773 | def quit(self, message=""): 774 | """Send a QUIT command.""" 775 | # Note that many IRC servers don't use your QUIT message 776 | # unless you've been connected for at least 5 minutes! 777 | self.send_raw("QUIT" + (message and (" :" + message))) 778 | 779 | def send_raw(self, string): 780 | """Send raw string to the server. 781 | 782 | The string will be padded with appropriate CR LF. 783 | From here everything need to be encoded in str from utf-8 784 | """ 785 | string=string.encode('utf-8') 786 | 787 | if self.socket is None: 788 | raise ServerNotConnectedError, "Not connected." 789 | try: 790 | if self.ssl: 791 | self.ssl.write(string + "\r\n") 792 | else: 793 | self.socket.send(string + "\r\n") 794 | if DEBUG: 795 | print "TO SERVER:", string 796 | except socket.error, x: 797 | # Ouch! 798 | self.disconnect("Connection reset by peer.") 799 | 800 | def squit(self, server, comment=""): 801 | """Send an SQUIT command.""" 802 | self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment))) 803 | 804 | def stats(self, statstype, server=""): 805 | """Send a STATS command.""" 806 | self.send_raw("STATS %s%s" % (statstype, server and (" " + server))) 807 | 808 | def time(self, server=""): 809 | """Send a TIME command.""" 810 | self.send_raw("TIME" + (server and (" " + server))) 811 | 812 | def topic(self, channel, new_topic=None): 813 | """Send a TOPIC command.""" 814 | if new_topic is None: 815 | self.send_raw("TOPIC " + channel) 816 | else: 817 | self.send_raw("TOPIC %s :%s" % (channel, new_topic)) 818 | 819 | def trace(self, target=""): 820 | """Send a TRACE command.""" 821 | self.send_raw("TRACE" + (target and (" " + target))) 822 | 823 | def user(self, username, realname): 824 | """Send a USER command.""" 825 | self.send_raw("USER %s 0 * :%s" % (username, realname)) 826 | 827 | def userhost(self, nicks): 828 | """Send a USERHOST command.""" 829 | self.send_raw("USERHOST " + ",".join(nicks)) 830 | 831 | def users(self, server=""): 832 | """Send a USERS command.""" 833 | self.send_raw("USERS" + (server and (" " + server))) 834 | 835 | def version(self, server=""): 836 | """Send a VERSION command.""" 837 | self.send_raw("VERSION" + (server and (" " + server))) 838 | 839 | def wallops(self, text): 840 | """Send a WALLOPS command.""" 841 | self.send_raw("WALLOPS :" + text) 842 | 843 | def who(self, target="", op=""): 844 | """Send a WHO command.""" 845 | self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o"))) 846 | 847 | def whois(self, targets): 848 | """Send a WHOIS command.""" 849 | self.send_raw("WHOIS " + ",".join(targets)) 850 | 851 | def whowas(self, nick, max="", server=""): 852 | """Send a WHOWAS command.""" 853 | self.send_raw("WHOWAS %s%s%s" % (nick, 854 | max and (" " + max), 855 | server and (" " + server))) 856 | 857 | class DCCConnectionError(IRCError): 858 | pass 859 | 860 | 861 | class DCCConnection(Connection): 862 | """This class represents a DCC connection. 863 | 864 | DCCConnection objects are instantiated by calling the dcc 865 | method on an IRC object. 866 | """ 867 | def __init__(self, irclibobj, dcctype): 868 | Connection.__init__(self, irclibobj) 869 | self.connected = 0 870 | self.passive = 0 871 | self.dcctype = dcctype 872 | self.peeraddress = None 873 | self.peerport = None 874 | 875 | def connect(self, address, port): 876 | """Connect/reconnect to a DCC peer. 877 | 878 | Arguments: 879 | address -- Host/IP address of the peer. 880 | 881 | port -- The port number to connect to. 882 | 883 | Returns the DCCConnection object. 884 | """ 885 | self.peeraddress = socket.gethostbyname(address) 886 | self.peerport = port 887 | self.socket = None 888 | self.previous_buffer = "" 889 | self.handlers = {} 890 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 891 | self.passive = 0 892 | try: 893 | self.socket.connect((self.peeraddress, self.peerport)) 894 | except socket.error, x: 895 | raise DCCConnectionError, "Couldn't connect to socket: %s" % x 896 | self.connected = 1 897 | if self.irclibobj.fn_to_add_socket: 898 | self.irclibobj.fn_to_add_socket(self.socket) 899 | return self 900 | 901 | def listen(self): 902 | """Wait for a connection/reconnection from a DCC peer. 903 | 904 | Returns the DCCConnection object. 905 | 906 | The local IP address and port are available as 907 | self.localaddress and self.localport. After connection from a 908 | peer, the peer address and port are available as 909 | self.peeraddress and self.peerport. 910 | """ 911 | self.previous_buffer = "" 912 | self.handlers = {} 913 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 914 | self.passive = 1 915 | try: 916 | self.socket.bind((socket.gethostbyname(socket.gethostname()), 0)) 917 | self.localaddress, self.localport = self.socket.getsockname() 918 | self.socket.listen(10) 919 | except socket.error, x: 920 | raise DCCConnectionError, "Couldn't bind socket: %s" % x 921 | return self 922 | 923 | def disconnect(self, message=""): 924 | """Hang up the connection and close the object. 925 | 926 | Arguments: 927 | 928 | message -- Quit message. 929 | """ 930 | if not self.connected: 931 | return 932 | 933 | self.connected = 0 934 | try: 935 | self.socket.close() 936 | except socket.error, x: 937 | pass 938 | self.socket = None 939 | self.irclibobj._handle_event( 940 | self, 941 | Event("dcc_disconnect", self.peeraddress, "", [message])) 942 | self.irclibobj._remove_connection(self) 943 | 944 | def process_data(self): 945 | """[Internal]""" 946 | 947 | if self.passive and not self.connected: 948 | conn, (self.peeraddress, self.peerport) = self.socket.accept() 949 | self.socket.close() 950 | self.socket = conn 951 | self.connected = 1 952 | if DEBUG: 953 | print "DCC connection from %s:%d" % ( 954 | self.peeraddress, self.peerport) 955 | self.irclibobj._handle_event( 956 | self, 957 | Event("dcc_connect", self.peeraddress, None, None)) 958 | return 959 | 960 | try: 961 | new_data = self.socket.recv(2**14) 962 | except socket.error, x: 963 | # The server hung up. 964 | self.disconnect("Connection reset by peer") 965 | return 966 | if not new_data: 967 | # Read nothing: connection must be down. 968 | self.disconnect("Connection reset by peer") 969 | return 970 | 971 | if self.dcctype == "chat": 972 | # The specification says lines are terminated with LF, but 973 | # it seems safer to handle CR LF terminations too. 974 | chunks = _linesep_regexp.split(self.previous_buffer + new_data) 975 | 976 | # Save the last, unfinished line. 977 | self.previous_buffer = chunks[-1] 978 | if len(self.previous_buffer) > 2**14: 979 | # Bad peer! Naughty peer! 980 | self.disconnect() 981 | return 982 | chunks = chunks[:-1] 983 | else: 984 | chunks = [new_data] 985 | 986 | command = "dccmsg" 987 | prefix = self.peeraddress 988 | target = None 989 | for chunk in chunks: 990 | if DEBUG: 991 | print "FROM PEER:", chunk 992 | arguments = [chunk] 993 | if DEBUG: 994 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 995 | command, prefix, target, arguments) 996 | self.irclibobj._handle_event( 997 | self, 998 | Event(command, prefix, target, arguments)) 999 | 1000 | def _get_socket(self): 1001 | """[Internal]""" 1002 | return self.socket 1003 | 1004 | def privmsg(self, string): 1005 | """Send data to DCC peer. 1006 | 1007 | The string will be padded with appropriate LF if it's a DCC 1008 | CHAT session. 1009 | """ 1010 | try: 1011 | self.socket.send(string) 1012 | if self.dcctype == "chat": 1013 | self.socket.send("\n") 1014 | if DEBUG: 1015 | print "TO PEER: %s\n" % string 1016 | except socket.error, x: 1017 | # Ouch! 1018 | self.disconnect("Connection reset by peer.") 1019 | 1020 | class SimpleIRCClient: 1021 | """A simple single-server IRC client class. 1022 | 1023 | This is an example of an object-oriented wrapper of the IRC 1024 | framework. A real IRC client can be made by subclassing this 1025 | class and adding appropriate methods. 1026 | 1027 | The method on_join will be called when a "join" event is created 1028 | (which is done when the server sends a JOIN messsage/command), 1029 | on_privmsg will be called for "privmsg" events, and so on. The 1030 | handler methods get two arguments: the connection object (same as 1031 | self.connection) and the event object. 1032 | 1033 | Instance attributes that can be used by sub classes: 1034 | 1035 | ircobj -- The IRC instance. 1036 | 1037 | connection -- The ServerConnection instance. 1038 | 1039 | dcc_connections -- A list of DCCConnection instances. 1040 | """ 1041 | def __init__(self): 1042 | self.ircobj = IRC() 1043 | self.connection = self.ircobj.server() 1044 | self.dcc_connections = [] 1045 | self.ircobj.add_global_handler("all_events", self._dispatcher, -10) 1046 | self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10) 1047 | 1048 | def _dispatcher(self, c, e): 1049 | """[Internal]""" 1050 | m = "on_" + e.eventtype() 1051 | if hasattr(self, m): 1052 | getattr(self, m)(c, e) 1053 | 1054 | def _dcc_disconnect(self, c, e): 1055 | self.dcc_connections.remove(c) 1056 | 1057 | def connect(self, server, port, nickname, password=None, username=None, 1058 | ircname=None, localaddress="", localport=0, ssl=False, ipv6=False): 1059 | """Connect/reconnect to a server. 1060 | 1061 | Arguments: 1062 | 1063 | server -- Server name. 1064 | 1065 | port -- Port number. 1066 | 1067 | nickname -- The nickname. 1068 | 1069 | password -- Password (if any). 1070 | 1071 | username -- The username. 1072 | 1073 | ircname -- The IRC name. 1074 | 1075 | localaddress -- Bind the connection to a specific local IP address. 1076 | 1077 | localport -- Bind the connection to a specific local port. 1078 | 1079 | ssl -- Enable support for ssl. 1080 | 1081 | ipv6 -- Enable support for ipv6. 1082 | 1083 | This function can be called to reconnect a closed connection. 1084 | """ 1085 | self.connection.connect(server, port, nickname, 1086 | password, username, ircname, 1087 | localaddress, localport, ssl, ipv6) 1088 | 1089 | def dcc_connect(self, address, port, dcctype="chat"): 1090 | """Connect to a DCC peer. 1091 | 1092 | Arguments: 1093 | 1094 | address -- IP address of the peer. 1095 | 1096 | port -- Port to connect to. 1097 | 1098 | Returns a DCCConnection instance. 1099 | """ 1100 | dcc = self.ircobj.dcc(dcctype) 1101 | self.dcc_connections.append(dcc) 1102 | dcc.connect(address, port) 1103 | return dcc 1104 | 1105 | def dcc_listen(self, dcctype="chat"): 1106 | """Listen for connections from a DCC peer. 1107 | 1108 | Returns a DCCConnection instance. 1109 | """ 1110 | dcc = self.ircobj.dcc(dcctype) 1111 | self.dcc_connections.append(dcc) 1112 | dcc.listen() 1113 | return dcc 1114 | 1115 | def start(self): 1116 | """Start the IRC client.""" 1117 | self.ircobj.process_forever() 1118 | 1119 | 1120 | class Event: 1121 | """Class representing an IRC event.""" 1122 | def __init__(self, eventtype, source, target, arguments=None): 1123 | """Constructor of Event objects. 1124 | 1125 | Arguments: 1126 | 1127 | eventtype -- A string describing the event. 1128 | 1129 | source -- The originator of the event (a nick mask or a server). 1130 | 1131 | target -- The target of the event (a nick or a channel). 1132 | 1133 | arguments -- Any event specific arguments. 1134 | """ 1135 | self._eventtype = eventtype 1136 | self._source = source 1137 | self._target = target 1138 | if arguments: 1139 | self._arguments = arguments 1140 | else: 1141 | self._arguments = [] 1142 | 1143 | def eventtype(self): 1144 | """Get the event type.""" 1145 | return self._eventtype 1146 | 1147 | def source(self): 1148 | """Get the event source.""" 1149 | return self._source 1150 | 1151 | def target(self): 1152 | """Get the event target.""" 1153 | return self._target 1154 | 1155 | def arguments(self): 1156 | """Get the event arguments.""" 1157 | return self._arguments 1158 | 1159 | _LOW_LEVEL_QUOTE = "\020" 1160 | _CTCP_LEVEL_QUOTE = "\134" 1161 | _CTCP_DELIMITER = "\001" 1162 | 1163 | _low_level_mapping = { 1164 | "0": "\000", 1165 | "n": "\n", 1166 | "r": "\r", 1167 | _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE 1168 | } 1169 | 1170 | _low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)") 1171 | 1172 | def mask_matches(nick, mask): 1173 | """Check if a nick matches a mask. 1174 | 1175 | Returns true if the nick matches, otherwise false. 1176 | """ 1177 | nick = irc_lower(nick) 1178 | mask = irc_lower(mask) 1179 | mask = mask.replace("\\", "\\\\") 1180 | for ch in ".$|[](){}+": 1181 | mask = mask.replace(ch, "\\" + ch) 1182 | mask = mask.replace("?", ".") 1183 | mask = mask.replace("*", ".*") 1184 | r = re.compile(mask, re.IGNORECASE) 1185 | return r.match(nick) 1186 | 1187 | _special = "-[]\\`^{}" 1188 | nick_characters = string.ascii_letters + string.digits + _special 1189 | _ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^", 1190 | string.ascii_lowercase + "{}|~") 1191 | 1192 | def irc_lower(s): 1193 | """Returns a lowercased string. 1194 | 1195 | The definition of lowercased comes from the IRC specification (RFC 1196 | 1459). 1197 | """ 1198 | return s.translate(_ircstring_translation) 1199 | 1200 | def _ctcp_dequote(message): 1201 | """[Internal] Dequote a message according to CTCP specifications. 1202 | 1203 | The function returns a list where each element can be either a 1204 | string (normal message) or a tuple of one or two strings (tagged 1205 | messages). If a tuple has only one element (ie is a singleton), 1206 | that element is the tag; otherwise the tuple has two elements: the 1207 | tag and the data. 1208 | 1209 | Arguments: 1210 | 1211 | message -- The message to be decoded. 1212 | """ 1213 | 1214 | def _low_level_replace(match_obj): 1215 | ch = match_obj.group(1) 1216 | 1217 | # If low_level_mapping doesn't have the character as key, we 1218 | # should just return the character. 1219 | return _low_level_mapping.get(ch, ch) 1220 | 1221 | if _LOW_LEVEL_QUOTE in message: 1222 | # Yup, there was a quote. Release the dequoter, man! 1223 | message = _low_level_regexp.sub(_low_level_replace, message) 1224 | 1225 | if _CTCP_DELIMITER not in message: 1226 | return [message] 1227 | else: 1228 | # Split it into parts. (Does any IRC client actually *use* 1229 | # CTCP stacking like this?) 1230 | chunks = message.split(_CTCP_DELIMITER) 1231 | 1232 | messages = [] 1233 | i = 0 1234 | while i < len(chunks)-1: 1235 | # Add message if it's non-empty. 1236 | if len(chunks[i]) > 0: 1237 | messages.append(chunks[i]) 1238 | 1239 | if i < len(chunks)-2: 1240 | # Aye! CTCP tagged data ahead! 1241 | messages.append(tuple(chunks[i+1].split(" ", 1))) 1242 | 1243 | i = i + 2 1244 | 1245 | if len(chunks) % 2 == 0: 1246 | # Hey, a lonely _CTCP_DELIMITER at the end! This means 1247 | # that the last chunk, including the delimiter, is a 1248 | # normal message! (This is according to the CTCP 1249 | # specification.) 1250 | messages.append(_CTCP_DELIMITER + chunks[-1]) 1251 | 1252 | return messages 1253 | 1254 | def is_channel(string): 1255 | """Check if a string is a channel name. 1256 | 1257 | Returns true if the argument is a channel name, otherwise false. 1258 | """ 1259 | return string and string[0] in "#&+!" 1260 | 1261 | def ip_numstr_to_quad(num): 1262 | """Convert an IP number as an integer given in ASCII 1263 | representation (e.g. '3232235521') to an IP address string 1264 | (e.g. '192.168.0.1').""" 1265 | n = long(num) 1266 | p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF, 1267 | n >> 8 & 0xFF, n & 0xFF])) 1268 | return ".".join(p) 1269 | 1270 | def ip_quad_to_numstr(quad): 1271 | """Convert an IP address string (e.g. '192.168.0.1') to an IP 1272 | number as an integer given in ASCII representation 1273 | (e.g. '3232235521').""" 1274 | p = map(long, quad.split(".")) 1275 | s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]) 1276 | if s[-1] == "L": 1277 | s = s[:-1] 1278 | return s 1279 | 1280 | def nm_to_n(s): 1281 | """Get the nick part of a nickmask. 1282 | 1283 | (The source of an Event is a nickmask.) 1284 | """ 1285 | return s.split("!")[0] 1286 | 1287 | def nm_to_uh(s): 1288 | """Get the userhost part of a nickmask. 1289 | 1290 | (The source of an Event is a nickmask.) 1291 | """ 1292 | return s.split("!")[1] 1293 | 1294 | def nm_to_h(s): 1295 | """Get the host part of a nickmask. 1296 | 1297 | (The source of an Event is a nickmask.) 1298 | """ 1299 | return s.split("@")[1] 1300 | 1301 | def nm_to_u(s): 1302 | """Get the user part of a nickmask. 1303 | 1304 | (The source of an Event is a nickmask.) 1305 | """ 1306 | s = s.split("!")[1] 1307 | return s.split("@")[0] 1308 | 1309 | def parse_nick_modes(mode_string): 1310 | """Parse a nick mode string. 1311 | 1312 | The function returns a list of lists with three members: sign, 1313 | mode and argument. The sign is \"+\" or \"-\". The argument is 1314 | always None. 1315 | 1316 | Example: 1317 | 1318 | >>> irclib.parse_nick_modes(\"+ab-c\") 1319 | [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]] 1320 | """ 1321 | 1322 | return _parse_modes(mode_string, "") 1323 | 1324 | def parse_channel_modes(mode_string): 1325 | """Parse a channel mode string. 1326 | 1327 | The function returns a list of lists with three members: sign, 1328 | mode and argument. The sign is \"+\" or \"-\". The argument is 1329 | None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\". 1330 | 1331 | Example: 1332 | 1333 | >>> irclib.parse_channel_modes(\"+ab-c foo\") 1334 | [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]] 1335 | """ 1336 | 1337 | return _parse_modes(mode_string, "bklvo") 1338 | 1339 | def _parse_modes(mode_string, unary_modes=""): 1340 | """[Internal]""" 1341 | modes = [] 1342 | arg_count = 0 1343 | 1344 | # State variable. 1345 | sign = "" 1346 | 1347 | a = mode_string.split() 1348 | if len(a) == 0: 1349 | return [] 1350 | else: 1351 | mode_part, args = a[0], a[1:] 1352 | 1353 | if mode_part[0] not in "+-": 1354 | return [] 1355 | for ch in mode_part: 1356 | if ch in "+-": 1357 | sign = ch 1358 | elif ch == " ": 1359 | collecting_arguments = 1 1360 | elif ch in unary_modes: 1361 | if len(args) >= arg_count + 1: 1362 | modes.append([sign, ch, args[arg_count]]) 1363 | arg_count = arg_count + 1 1364 | else: 1365 | modes.append([sign, ch, None]) 1366 | else: 1367 | modes.append([sign, ch, None]) 1368 | return modes 1369 | 1370 | def _ping_ponger(connection, event): 1371 | """[Internal]""" 1372 | connection.pong(event.target()) 1373 | 1374 | # Numeric table mostly stolen from the Perl IRC module (Net::IRC). 1375 | numeric_events = { 1376 | "001": "welcome", 1377 | "002": "yourhost", 1378 | "003": "created", 1379 | "004": "myinfo", 1380 | "005": "featurelist", # XXX 1381 | "200": "tracelink", 1382 | "201": "traceconnecting", 1383 | "202": "tracehandshake", 1384 | "203": "traceunknown", 1385 | "204": "traceoperator", 1386 | "205": "traceuser", 1387 | "206": "traceserver", 1388 | "207": "traceservice", 1389 | "208": "tracenewtype", 1390 | "209": "traceclass", 1391 | "210": "tracereconnect", 1392 | "211": "statslinkinfo", 1393 | "212": "statscommands", 1394 | "213": "statscline", 1395 | "214": "statsnline", 1396 | "215": "statsiline", 1397 | "216": "statskline", 1398 | "217": "statsqline", 1399 | "218": "statsyline", 1400 | "219": "endofstats", 1401 | "221": "umodeis", 1402 | "231": "serviceinfo", 1403 | "232": "endofservices", 1404 | "233": "service", 1405 | "234": "servlist", 1406 | "235": "servlistend", 1407 | "241": "statslline", 1408 | "242": "statsuptime", 1409 | "243": "statsoline", 1410 | "244": "statshline", 1411 | "250": "luserconns", 1412 | "251": "luserclient", 1413 | "252": "luserop", 1414 | "253": "luserunknown", 1415 | "254": "luserchannels", 1416 | "255": "luserme", 1417 | "256": "adminme", 1418 | "257": "adminloc1", 1419 | "258": "adminloc2", 1420 | "259": "adminemail", 1421 | "261": "tracelog", 1422 | "262": "endoftrace", 1423 | "263": "tryagain", 1424 | "265": "n_local", 1425 | "266": "n_global", 1426 | "300": "none", 1427 | "301": "away", 1428 | "302": "userhost", 1429 | "303": "ison", 1430 | "305": "unaway", 1431 | "306": "nowaway", 1432 | "311": "whoisuser", 1433 | "312": "whoisserver", 1434 | "313": "whoisoperator", 1435 | "314": "whowasuser", 1436 | "315": "endofwho", 1437 | "316": "whoischanop", 1438 | "317": "whoisidle", 1439 | "318": "endofwhois", 1440 | "319": "whoischannels", 1441 | "321": "liststart", 1442 | "322": "list", 1443 | "323": "listend", 1444 | "324": "channelmodeis", 1445 | "329": "channelcreate", 1446 | "331": "notopic", 1447 | "332": "currenttopic", 1448 | "333": "topicinfo", 1449 | "341": "inviting", 1450 | "342": "summoning", 1451 | "346": "invitelist", 1452 | "347": "endofinvitelist", 1453 | "348": "exceptlist", 1454 | "349": "endofexceptlist", 1455 | "351": "version", 1456 | "352": "whoreply", 1457 | "353": "namreply", 1458 | "361": "killdone", 1459 | "362": "closing", 1460 | "363": "closeend", 1461 | "364": "links", 1462 | "365": "endoflinks", 1463 | "366": "endofnames", 1464 | "367": "banlist", 1465 | "368": "endofbanlist", 1466 | "369": "endofwhowas", 1467 | "371": "info", 1468 | "372": "motd", 1469 | "373": "infostart", 1470 | "374": "endofinfo", 1471 | "375": "motdstart", 1472 | "376": "endofmotd", 1473 | "377": "motd2", # 1997-10-16 -- tkil 1474 | "381": "youreoper", 1475 | "382": "rehashing", 1476 | "384": "myportis", 1477 | "391": "time", 1478 | "392": "usersstart", 1479 | "393": "users", 1480 | "394": "endofusers", 1481 | "395": "nousers", 1482 | "401": "nosuchnick", 1483 | "402": "nosuchserver", 1484 | "403": "nosuchchannel", 1485 | "404": "cannotsendtochan", 1486 | "405": "toomanychannels", 1487 | "406": "wasnosuchnick", 1488 | "407": "toomanytargets", 1489 | "409": "noorigin", 1490 | "411": "norecipient", 1491 | "412": "notexttosend", 1492 | "413": "notoplevel", 1493 | "414": "wildtoplevel", 1494 | "421": "unknowncommand", 1495 | "422": "nomotd", 1496 | "423": "noadmininfo", 1497 | "424": "fileerror", 1498 | "431": "nonicknamegiven", 1499 | "432": "erroneusnickname", # Thiss iz how its speld in thee RFC. 1500 | "433": "nicknameinuse", 1501 | "436": "nickcollision", 1502 | "437": "unavailresource", # "Nick temporally unavailable" 1503 | "441": "usernotinchannel", 1504 | "442": "notonchannel", 1505 | "443": "useronchannel", 1506 | "444": "nologin", 1507 | "445": "summondisabled", 1508 | "446": "usersdisabled", 1509 | "451": "notregistered", 1510 | "461": "needmoreparams", 1511 | "462": "alreadyregistered", 1512 | "463": "nopermforhost", 1513 | "464": "passwdmismatch", 1514 | "465": "yourebannedcreep", # I love this one... 1515 | "466": "youwillbebanned", 1516 | "467": "keyset", 1517 | "471": "channelisfull", 1518 | "472": "unknownmode", 1519 | "473": "inviteonlychan", 1520 | "474": "bannedfromchan", 1521 | "475": "badchannelkey", 1522 | "476": "badchanmask", 1523 | "477": "nochanmodes", # "Channel doesn't support modes" 1524 | "478": "banlistfull", 1525 | "481": "noprivileges", 1526 | "482": "chanoprivsneeded", 1527 | "483": "cantkillserver", 1528 | "484": "restricted", # Connection is restricted 1529 | "485": "uniqopprivsneeded", 1530 | "491": "nooperhost", 1531 | "492": "noservicehost", 1532 | "501": "umodeunknownflag", 1533 | "502": "usersdontmatch", 1534 | } 1535 | 1536 | generated_events = [ 1537 | # Generated events 1538 | "dcc_connect", 1539 | "dcc_disconnect", 1540 | "dccmsg", 1541 | "disconnect", 1542 | "ctcp", 1543 | "ctcpreply", 1544 | ] 1545 | 1546 | protocol_events = [ 1547 | # IRC protocol events 1548 | "error", 1549 | "join", 1550 | "kick", 1551 | "mode", 1552 | "part", 1553 | "ping", 1554 | "privmsg", 1555 | "privnotice", 1556 | "pubmsg", 1557 | "pubnotice", 1558 | "quit", 1559 | "invite", 1560 | "pong", 1561 | ] 1562 | 1563 | all_events = generated_events + protocol_events + numeric_events.values() 1564 | -------------------------------------------------------------------------------- /python-irclib.spec: -------------------------------------------------------------------------------- 1 | Summary: A set of Python modules for IRC support. 2 | Name: python-irclib 3 | Version: 0.4.8 4 | Release: 1 5 | Group: Development/Libraries 6 | License: LGPL 7 | URL: http://python-irclib.sourceforge.net 8 | Source: %{name}-%{version}.tar.gz 9 | BuildRoot: %{_tmppath}/%{name}-root 10 | Requires: python 11 | BuildPrereq: python 12 | BuildArch: noarch 13 | 14 | %description 15 | This library is intended to encapsulate the IRC protocol at a quite 16 | low level. It provides an event-driven IRC client framework. It has 17 | a fairly thorough support for the basic IRC protocol, CTCP and DCC 18 | connections. 19 | 20 | %prep 21 | %setup -q 22 | chmod 644 * 23 | 24 | %build 25 | python -c "import py_compile; py_compile.compile('irclib.py')" 26 | python -c "import py_compile; py_compile.compile('ircbot.py')" 27 | 28 | %install 29 | [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT 30 | %{__mkdir_p} $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages 31 | %{__install} -m 644 irclib.py* $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages 32 | %{__install} -m 644 ircbot.py* $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages 33 | 34 | %clean 35 | [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT 36 | 37 | %files 38 | %defattr(-,root,root) 39 | %doc README ChangeLog COPYING irccat irccat2 servermap testbot.py dccsend dccreceive 40 | /usr/lib/python*/site-packages/* 41 | 42 | %changelog 43 | * Sat Sep 11 2008 Keltus 0.4.8-1 44 | - upgraded to 0.4.8 45 | 46 | * Sat Aug 29 2008 Keltus 0.4.7-1 47 | - upgraded to 0.4.7 48 | 49 | * Sat Dec 24 2005 Keltus 0.4.6-1 50 | - upgraded to 0.4.6 51 | 52 | * Wed May 18 2005 Keltus 0.4.5-1 53 | - upgraded to 0.4.5 54 | 55 | * Wed Feb 23 2005 Keltus 0.4.4-1 56 | - upgraded to 0.4.4 57 | 58 | * Sun Jan 19 2005 Joel Rosdahl 0.4.3-1 59 | - upgraded to 0.4.3 60 | 61 | * Fri Jul 9 2004 Joel Rosdahl 0.4.2-1 62 | - upgraded to 0.4.2 63 | 64 | * Thu Oct 30 2003 Joel Rosdahl 0.4.1-1 65 | - upgraded to 0.4.1 66 | 67 | * Mon Sep 1 2002 Gary Benson 0.4.0-1 68 | - upgraded to 0.4.0 69 | 70 | * Wed Feb 20 2002 Gary Benson 0.3.4-1 71 | - upgraded to 0.3.4 72 | 73 | * Wed Feb 20 2002 Gary Benson 0.3.3-1 74 | - initial revision 75 | -------------------------------------------------------------------------------- /servermap: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Example program using irclib.py. 4 | # 5 | # Copyright (C) 1999-2002 Joel Rosdahl 6 | # 7 | # This library is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU Lesser General Public 9 | # License as published by the Free Software Foundation; either 10 | # version 2.1 of the License, or (at your option) any later version. 11 | # 12 | # This library is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public 18 | # License along with this library; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # 21 | # Joel Rosdahl 22 | # 23 | # servermap connects to an IRC server and finds out what other IRC 24 | # servers there are in the net and prints a tree-like map of their 25 | # interconnections. 26 | # 27 | # Example: 28 | # 29 | # % ./servermap irc.dal.net somenickname 30 | # Connecting to server... 31 | # Getting links... 32 | # 33 | # 26 servers (18 leaves and 8 hubs) 34 | # 35 | # splitrock.tx.us.dal.net 36 | # `-vader.ny.us.dal.net 37 | # |-twisted.ma.us.dal.net 38 | # |-sodre.nj.us.dal.net 39 | # |-glass.oh.us.dal.net 40 | # |-distant.ny.us.dal.net 41 | # | |-algo.se.eu.dal.net 42 | # | | |-borg.se.eu.dal.net 43 | # | | | `-ced.se.eu.dal.net 44 | # | | |-viking.no.eu.dal.net 45 | # | | |-inco.fr.eu.dal.net 46 | # | | |-paranoia.se.eu.dal.net 47 | # | | |-gaston.se.eu.dal.net 48 | # | | | `-powertech.no.eu.dal.net 49 | # | | `-algo-u.se.eu.dal.net 50 | # | |-philly.pa.us.dal.net 51 | # | |-liberty.nj.us.dal.net 52 | # | `-jade.va.us.dal.net 53 | # `-journey.ca.us.dal.net 54 | # |-ion.va.us.dal.net 55 | # |-dragons.ca.us.dal.net 56 | # |-toronto.on.ca.dal.net 57 | # | `-netropolis-r.uk.eu.dal.net 58 | # | |-traced.de.eu.dal.net 59 | # | `-lineone.uk.eu.dal.net 60 | # `-omega.ca.us.dal.net 61 | 62 | import irclib 63 | import sys 64 | 65 | if len(sys.argv) != 3: 66 | print "Usage: servermap " 67 | sys.exit(1) 68 | 69 | links = [] 70 | 71 | def on_connect(connection, event): 72 | sys.stdout.write("\nGetting links...") 73 | sys.stdout.flush() 74 | connection.links() 75 | 76 | def on_passwdmismatch(connection, event): 77 | print "Password required." 78 | sys.exit(1) 79 | 80 | def on_links(connection, event): 81 | global links 82 | 83 | links.append((event.arguments()[0], 84 | event.arguments()[1], 85 | event.arguments()[2])) 86 | 87 | def on_endoflinks(connection, event): 88 | global links 89 | 90 | print "\n" 91 | 92 | m = {} 93 | for (to_node, from_node, desc) in links: 94 | if from_node != to_node: 95 | m[from_node] = m.get(from_node, []) + [to_node] 96 | 97 | if connection.get_server_name() in m: 98 | if len(m[connection.get_server_name()]) == 1: 99 | hubs = len(m) - 1 100 | else: 101 | hubs = len(m) 102 | else: 103 | hubs = 0 104 | 105 | print "%d servers (%d leaves and %d hubs)\n" % (len(links), len(links)-hubs, hubs) 106 | 107 | print_tree(0, [], connection.get_server_name(), m) 108 | connection.quit("Using irclib.py") 109 | 110 | def on_disconnect(connection, event): 111 | sys.exit(0) 112 | 113 | def indent_string(level, active_levels, last): 114 | if level == 0: 115 | return "" 116 | s = "" 117 | for i in range(level-1): 118 | if i in active_levels: 119 | s = s + "| " 120 | else: 121 | s = s + " " 122 | if last: 123 | s = s + "`-" 124 | else: 125 | s = s + "|-" 126 | return s 127 | 128 | def print_tree(level, active_levels, root, map, last=0): 129 | sys.stdout.write(indent_string(level, active_levels, last) 130 | + root + "\n") 131 | if root in map: 132 | list = map[root] 133 | for r in list[:-1]: 134 | print_tree(level+1, active_levels[:]+[level], r, map) 135 | print_tree(level+1, active_levels[:], list[-1], map, 1) 136 | 137 | s = sys.argv[1].split(":", 1) 138 | server = s[0] 139 | if len(s) == 2: 140 | try: 141 | port = int(s[1]) 142 | except ValueError: 143 | print "Error: Erroneous port." 144 | sys.exit(1) 145 | else: 146 | port = 6667 147 | nickname = sys.argv[2] 148 | 149 | irc = irclib.IRC() 150 | sys.stdout.write("Connecting to server...") 151 | sys.stdout.flush() 152 | try: 153 | c = irc.server().connect(server, port, nickname) 154 | except irclib.ServerConnectionError, x: 155 | print x 156 | sys.exit(1) 157 | 158 | c.add_global_handler("welcome", on_connect) 159 | c.add_global_handler("passwdmismatch", on_passwdmismatch) 160 | c.add_global_handler("links", on_links) 161 | c.add_global_handler("endoflinks", on_endoflinks) 162 | c.add_global_handler("disconnect", on_disconnect) 163 | 164 | irc.process_forever() 165 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from distutils.core import setup 4 | setup(name="python-irclib", 5 | version="0.4.8", 6 | py_modules=["irclib", "ircbot"], 7 | author="Joel Rosdahl", 8 | author_email="joel@rosdahl.net", 9 | url="http://python-irclib.sourceforge.net") 10 | -------------------------------------------------------------------------------- /testbot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Example program using ircbot.py. 4 | # 5 | # Joel Rosdahl 6 | 7 | """A simple example bot. 8 | 9 | This is an example bot that uses the SingleServerIRCBot class from 10 | ircbot.py. The bot enters a channel and listens for commands in 11 | private messages and channel traffic. Commands in channel messages 12 | are given by prefixing the text by the bot name followed by a colon. 13 | It also responds to DCC CHAT invitations and echos data sent in such 14 | sessions. 15 | 16 | The known commands are: 17 | 18 | stats -- Prints some channel information. 19 | 20 | disconnect -- Disconnect the bot. The bot will try to reconnect 21 | after 60 seconds. 22 | 23 | die -- Let the bot cease to exist. 24 | 25 | dcc -- Let the bot invite you to a DCC CHAT connection. 26 | """ 27 | 28 | from ircbot import SingleServerIRCBot 29 | from irclib import nm_to_n, nm_to_h, irc_lower, ip_numstr_to_quad, ip_quad_to_numstr 30 | 31 | class TestBot(SingleServerIRCBot): 32 | def __init__(self, channel, nickname, server, port=6667): 33 | SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname) 34 | self.channel = channel 35 | 36 | def on_nicknameinuse(self, c, e): 37 | c.nick(c.get_nickname() + "_") 38 | 39 | def on_welcome(self, c, e): 40 | c.join(self.channel) 41 | 42 | def on_privmsg(self, c, e): 43 | self.do_command(e, e.arguments()[0]) 44 | 45 | def on_pubmsg(self, c, e): 46 | a = e.arguments()[0].split(":", 1) 47 | if len(a) > 1 and irc_lower(a[0]) == irc_lower(self.connection.get_nickname()): 48 | self.do_command(e, a[1].strip()) 49 | return 50 | 51 | def on_dccmsg(self, c, e): 52 | c.privmsg("You said: " + e.arguments()[0]) 53 | 54 | def on_dccchat(self, c, e): 55 | if len(e.arguments()) != 2: 56 | return 57 | args = e.arguments()[1].split() 58 | if len(args) == 4: 59 | try: 60 | address = ip_numstr_to_quad(args[2]) 61 | port = int(args[3]) 62 | except ValueError: 63 | return 64 | self.dcc_connect(address, port) 65 | 66 | def do_command(self, e, cmd): 67 | nick = nm_to_n(e.source()) 68 | c = self.connection 69 | 70 | if cmd == "disconnect": 71 | self.disconnect() 72 | elif cmd == "die": 73 | self.die() 74 | elif cmd == "stats": 75 | for chname, chobj in self.channels.items(): 76 | c.notice(nick, "--- Channel statistics ---") 77 | c.notice(nick, "Channel: " + chname) 78 | users = chobj.users() 79 | users.sort() 80 | c.notice(nick, "Users: " + ", ".join(users)) 81 | opers = chobj.opers() 82 | opers.sort() 83 | c.notice(nick, "Opers: " + ", ".join(opers)) 84 | voiced = chobj.voiced() 85 | voiced.sort() 86 | c.notice(nick, "Voiced: " + ", ".join(voiced)) 87 | elif cmd == "dcc": 88 | dcc = self.dcc_listen() 89 | c.ctcp("DCC", nick, "CHAT chat %s %d" % ( 90 | ip_quad_to_numstr(dcc.localaddress), 91 | dcc.localport)) 92 | else: 93 | c.notice(nick, "Not understood: " + cmd) 94 | 95 | def main(): 96 | import sys 97 | if len(sys.argv) != 4: 98 | print "Usage: testbot " 99 | sys.exit(1) 100 | 101 | s = sys.argv[1].split(":", 1) 102 | server = s[0] 103 | if len(s) == 2: 104 | try: 105 | port = int(s[1]) 106 | except ValueError: 107 | print "Error: Erroneous port." 108 | sys.exit(1) 109 | else: 110 | port = 6667 111 | channel = sys.argv[2] 112 | nickname = sys.argv[3] 113 | 114 | bot = TestBot(channel, nickname, server, port) 115 | bot.start() 116 | 117 | if __name__ == "__main__": 118 | main() 119 | --------------------------------------------------------------------------------