├── lib ├── irclib │ ├── .cvsignore │ ├── ChangeLog │ ├── setup.py.in │ ├── Makefile │ ├── irccat │ ├── irccat2 │ ├── python-irclib.spec.in │ ├── dccreceive │ ├── dccsend │ ├── README │ ├── testbot.py │ ├── servermap │ ├── ircbot.py │ └── COPYING ├── pyborg │ ├── ircbot.py │ ├── irclib.py │ ├── atomicfile.py │ ├── pyborg-pipein.py │ ├── convert.py │ ├── pyborg-filein.py │ ├── convert2.py │ ├── pyborg-linein.py │ ├── cfgfile.py │ ├── pyborg-telnet.py │ ├── ChangeLog │ ├── pyborg-msnp.py │ ├── readme │ ├── copying.txt │ ├── pyborg-irc.py │ └── pyborg.py └── atomicfile │ └── atomicfile.py ├── .gitignore ├── start.sh └── README /lib/irclib/.cvsignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | data 4 | -------------------------------------------------------------------------------- /lib/pyborg/ircbot.py: -------------------------------------------------------------------------------- 1 | ../irclib/ircbot.py -------------------------------------------------------------------------------- /lib/pyborg/irclib.py: -------------------------------------------------------------------------------- 1 | ../irclib/irclib.py -------------------------------------------------------------------------------- /lib/pyborg/atomicfile.py: -------------------------------------------------------------------------------- 1 | ../atomicfile/atomicfile.py -------------------------------------------------------------------------------- /lib/irclib/ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdrewery/PyBorg/HEAD/lib/irclib/ChangeLog -------------------------------------------------------------------------------- /lib/irclib/setup.py.in: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from distutils.core import setup 4 | setup(name="python-irclib", 5 | version="%%VERSION%%", 6 | py_modules=["irclib", "ircbot"], 7 | author="Joel Rosdahl", 8 | author_email="joel@rosdahl.net", 9 | url="http://python-irclib.sourceforge.net") 10 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | PYTHON=$(which pypy 2>/dev/null) 4 | if [ -z "$PYTHON" ]; then 5 | PYTHON=python 6 | fi 7 | 8 | # Ensure the proper symlinks are there. Github's .zip archiving 9 | # does not properly extract them. 10 | 11 | if ! [ -L lib/pyborg/irclib.py ]; then 12 | rm -f lib/pyborg/irclib.py lib/pyborg/ircbot.py lib/pyborg/atomicfile.py 13 | ln -s ../irclib/irclib.py lib/pyborg/irclib.py 14 | ln -s ../irclib/ircbot.py lib/pyborg/ircbot.py 15 | ln -s ../atomicfile/atomicfile.py lib/pyborg/atomicfile.py 16 | fi 17 | 18 | DATADIR=${1:-data} 19 | PYBORG=${2:-pyborg-irc.py} 20 | 21 | mkdir $DATADIR 2>/dev/null || : 22 | cd ${DATADIR}/ 23 | 24 | exec nice ${PYTHON} ../lib/pyborg/${PYBORG} 25 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg-pipein.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg Offline line input module 5 | # 6 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 7 | # 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | import string 24 | import sys 25 | 26 | import pyborg 27 | 28 | class ModLineIn: 29 | """ 30 | Module to interface piped input and output with the PyBorg learn module. 31 | """ 32 | 33 | def __init__(self, my_pyborg): 34 | self.pyborg = my_pyborg 35 | self.start() 36 | 37 | def start(self): 38 | while 1: 39 | try: 40 | body = raw_input() 41 | except (KeyboardInterrupt, EOFError), e: 42 | print 43 | return 44 | if body == "": 45 | continue 46 | # Pass message to borg 47 | self.pyborg.process_msg(self, body, 0, 1, ()) 48 | 49 | if __name__ == "__main__": 50 | # start the pyborg 51 | my_pyborg = pyborg.pyborg() 52 | try: 53 | ModLineIn(my_pyborg) 54 | except SystemExit: 55 | pass 56 | my_pyborg.save_all() 57 | del my_pyborg -------------------------------------------------------------------------------- /lib/pyborg/convert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # Use to convert pyborg 0.9.10 and 0.9.11 dictionaries to the 5 | # version 1.0.0+ format. 6 | # 7 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 8 | # 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation; either version 2 13 | # of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the Free Software 22 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 | # 24 | import sys 25 | import marshal 26 | import struct 27 | import string 28 | 29 | # Read the dictionary 30 | print "Reading dictionary stage 1..." 31 | 32 | try: 33 | f = open("lines.dat", "r") 34 | s = f.read() 35 | f.close() 36 | lines = marshal.loads(s) 37 | del s 38 | except (EOFError, IOError), e: 39 | print "Error reading dictionary." 40 | sys.exit() 41 | 42 | print "working..." 43 | for x in lines.keys(): 44 | # clean up whitespace mess 45 | line = lines[x] 46 | words = string.split(line) 47 | lines[x] = string.join(words, " ") 48 | 49 | print "Saving Dictionary..." 50 | 51 | f = open("lines.dat", "w") 52 | s = marshal.dumps(lines) 53 | f.write(s) 54 | f.close() 55 | 56 | # Read the dictionary 57 | print "Reading dictionary stage 2..." 58 | 59 | try: 60 | f = open("words.dat", "r") 61 | s = f.read() 62 | f.close() 63 | words = marshal.loads(s) 64 | del s 65 | except (EOFError, IOError), e: 66 | print "Error reading dictionary." 67 | sys.exit() 68 | 69 | print "working..." 70 | for key in words.keys(): 71 | # marshallise it: 72 | y = [] 73 | for i in words[key]: 74 | y.append(struct.pack("lH", i[0], i[1])) 75 | words[key] = y 76 | 77 | print "Saving Dictionary..." 78 | 79 | f = open("words.dat", "w") 80 | s = marshal.dumps(words) 81 | f.write(s) 82 | f.close() 83 | print "Done." 84 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg-filein.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg ascii file input module 5 | # 6 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 7 | # 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | import string 24 | import sys 25 | 26 | import pyborg 27 | 28 | class ModFileIn: 29 | """ 30 | Module for file input. Learning from ASCII text files. 31 | """ 32 | 33 | # Command list for this module 34 | commandlist = "FileIn Module Commands:\nNone" 35 | commanddict = {} 36 | 37 | def __init__(self, Borg, args): 38 | 39 | f = open(args[1], "r") 40 | buffer = f.read() 41 | f.close() 42 | 43 | print "I knew "+`Borg.settings.num_words`+" words ("+`len(Borg.lines)`+" lines) before reading "+sys.argv[1] 44 | buffer = pyborg.filter_message(buffer, Borg) 45 | # Learn from input 46 | try: 47 | print buffer 48 | Borg.learn(buffer) 49 | except KeyboardInterrupt, e: 50 | # Close database cleanly 51 | print "Premature termination :-(" 52 | print "I know "+`Borg.settings.num_words`+" words ("+`len(Borg.lines)`+" lines) now." 53 | del Borg 54 | 55 | def shutdown(self): 56 | pass 57 | 58 | def start(self): 59 | sys.exit() 60 | 61 | def output(self, message, args): 62 | pass 63 | 64 | if __name__ == "__main__": 65 | if len(sys.argv) < 2: 66 | print "Specify a filename." 67 | sys.exit() 68 | # start the pyborg 69 | my_pyborg = pyborg.pyborg() 70 | ModFileIn(my_pyborg, sys.argv) 71 | my_pyborg.save_all() 72 | del my_pyborg 73 | -------------------------------------------------------------------------------- /lib/pyborg/convert2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg: The python AI bot. 5 | # 6 | # 7 | # Use to convert pyborg 1.0.6 dictionaries to the 8 | # version 1.1.0+ format. 9 | # 10 | # Copyright (c) 2006 Sebastien Dailly 11 | # 12 | # 13 | # This program is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU General Public License 15 | # as published by the Free Software Foundation; either version 2 16 | # of the License, or (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 26 | # 27 | import sys 28 | import marshal 29 | import struct 30 | import string 31 | 32 | print "Reading dictionary..." 33 | try: 34 | f = open("words.dat", "rb") 35 | s = f.read() 36 | f.close() 37 | self.words = marshal.loads(s) 38 | del s 39 | f = open("lines.dat", "rb") 40 | s = f.read() 41 | f.close() 42 | self.lines = marshal.loads(s) 43 | del s 44 | except (EOFError, IOError), e: 45 | pass 46 | 47 | 48 | #change the contexts here ! 49 | compteur = 0 50 | for x in self.lines.keys(): 51 | self.lines[x]=[self.lines[x],1] 52 | compteur = 1 53 | if compteur != 0: 54 | print "Contexts update done" 55 | 56 | print "Writing dictionary..." 57 | 58 | zfile = zipfile.ZipFile('archive.zip','r') 59 | for filename in zfile.namelist(): 60 | data = zfile.read(filename) 61 | file = open(filename, 'w+b') 62 | file.write(data) 63 | file.close() 64 | 65 | f = open("words.dat", "wb") 66 | s = marshal.dumps(self.words) 67 | f.write(s) 68 | f.close() 69 | f = open("lines.dat", "wb") 70 | s = marshal.dumps(self.lines) 71 | f.write(s) 72 | f.close() 73 | 74 | #zip the files 75 | f = zipfile.ZipFile('archive.zip','w',zipfile.ZIP_DEFLATED) 76 | f.write('words.dat') 77 | f.write('lines.dat') 78 | f.close() 79 | 80 | try: 81 | os.remove('words.dat') 82 | os.remove('lines.dat') 83 | except (OSError, IOError), e: 84 | print "could not remove the files" 85 | 86 | f.close() 87 | -------------------------------------------------------------------------------- /lib/irclib/python-irclib.spec.in: -------------------------------------------------------------------------------- 1 | Summary: A set of Python modules for IRC support. 2 | Name: python-irclib 3 | Version: %%VERSION%% 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 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg-linein.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg Offline line input module 5 | # 6 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 7 | # 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | import string 24 | import sys 25 | 26 | import pyborg 27 | 28 | class ModLineIn: 29 | """ 30 | Module to interface console input and output with the PyBorg learn 31 | and reply modules. Allows offline chat with PyBorg. 32 | """ 33 | # Command list for this module 34 | commandlist = "LineIn Module Commands:\n!quit" 35 | commanddict = { "quit": "Usage: !quit\nQuits pyborg and saves the dictionary" } 36 | 37 | def __init__(self, my_pyborg): 38 | self.pyborg = my_pyborg 39 | self.start() 40 | 41 | def start(self): 42 | print "PyBorg offline chat!\n" 43 | print "Type !quit to leave" 44 | print "What is your name" 45 | name = raw_input("? ") 46 | while 1: 47 | try: 48 | body = raw_input("> ") 49 | except (KeyboardInterrupt, EOFError), e: 50 | print 51 | return 52 | if body == "": 53 | continue 54 | if body[0] == "!": 55 | if self.linein_commands(body): 56 | continue 57 | # Pass message to borg 58 | self.pyborg.process_msg(self, body, 100, 1, (name), owner = 1) 59 | 60 | def linein_commands(self, body): 61 | command_list = string.split(body) 62 | command_list[0] = string.lower(command_list[0]) 63 | 64 | if command_list[0] == "!quit": 65 | sys.exit(0) 66 | 67 | def output(self, message, args): 68 | """ 69 | Output a line of text. 70 | """ 71 | message = message.replace("#nick", args) 72 | print message 73 | 74 | if __name__ == "__main__": 75 | # start the pyborg 76 | my_pyborg = pyborg.pyborg() 77 | try: 78 | ModLineIn(my_pyborg) 79 | except SystemExit: 80 | pass 81 | my_pyborg.save_all() 82 | del my_pyborg 83 | -------------------------------------------------------------------------------- /lib/atomicfile/atomicfile.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | import errno 4 | import os 5 | import tempfile 6 | 7 | 8 | umask = os.umask(0) 9 | os.umask(umask) 10 | 11 | 12 | def copymode(src, dst, mode=None): 13 | """ 14 | Copy the file mode from the file at path |src| to |dst|. 15 | If |src| doesn't exist, we're using |mode| instead. If |mode| is None, 16 | we're using |umask|. 17 | """ 18 | try: 19 | st_mode = os.lstat(src).st_mode & 0o777 20 | except OSError as inst: 21 | if inst.errno != errno.ENOENT: 22 | raise 23 | st_mode = mode 24 | if st_mode is None: 25 | st_mode = ~umask 26 | st_mode &= 0o666 27 | os.chmod(dst, st_mode) 28 | 29 | 30 | def mktemp(name, createmode=None): 31 | """ 32 | Create a temporary file with the similar |name|. 33 | The permission bits are copied from the original file or |createmode|. 34 | 35 | Returns: the name of the temporary file. 36 | """ 37 | d, fn = os.path.split(name) 38 | fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d) 39 | os.close(fd) 40 | 41 | # Temporary files are created with mode 0600, which is usually not 42 | # what we want. If the original file already exists, just copy 43 | # its mode. Otherwise, manually obey umask. 44 | copymode(name, temp, createmode) 45 | return temp 46 | 47 | 48 | class AtomicFile(object): 49 | """ 50 | Writeable file object that atomically updates a file. 51 | 52 | All writes will go to a temporary file. 53 | Call close() when you are done writing, and AtomicFile will rename 54 | the temporary copy to the original name, making the changes visible. 55 | If the object is destroyed without being closed, all your writes are 56 | discarded. 57 | """ 58 | def __init__(self, name, mode="w+b", createmode=None): 59 | self.__name = name # permanent name 60 | self._tempname = mktemp(name, createmode=createmode) 61 | self._fp = open(self._tempname, mode) 62 | 63 | # delegated methods 64 | self.write = self._fp.write 65 | self.fileno = self._fp.fileno 66 | 67 | def __enter__(self): 68 | return self 69 | 70 | def __exit__(self, exc_type, exc_value, exc_tb): 71 | if exc_type: 72 | return 73 | self.close() 74 | 75 | def close(self): 76 | if not self._fp.closed: 77 | os.fsync(self._fp.fileno()) 78 | self._fp.close() 79 | os.rename(self._tempname, self.__name) 80 | 81 | def discard(self): 82 | if not self._fp.closed: 83 | try: 84 | os.unlink(self._tempname) 85 | except OSError: 86 | pass 87 | self._fp.close() 88 | 89 | def __del__(self): 90 | if getattr(self, "_fp", None): # constructor actually did something 91 | self.discard() 92 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/pyborg/cfgfile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg: The python AI bot. 5 | # 6 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 7 | # 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | import string 24 | import os 25 | from atomicfile import AtomicFile 26 | 27 | def _load_config(filename): 28 | """ 29 | Load a config file returning dictionary of variables. 30 | """ 31 | try: 32 | f = open(filename, "r") 33 | except IOError, e: 34 | return None 35 | 36 | stuff = {} 37 | line = 0 38 | 39 | while 1: 40 | line = line + 1 41 | s = f.readline() 42 | if s=="": 43 | break 44 | if s[0]=="#": 45 | continue 46 | 47 | #read if the string is above multiple lines 48 | while s.rfind("\\") > -1: 49 | s = s[:s.rfind("\\")] + f.readline() 50 | line = line + 1 51 | 52 | s = string.split(s, "=") 53 | if len(s) != 2: 54 | print "Malformed line in %s line %d" % (filename, line) 55 | print s 56 | continue 57 | stuff[string.strip(s[0])] = eval(string.strip(string.join(s[1:], "="))) 58 | return stuff 59 | 60 | def _save_config(filename, fields): 61 | """ 62 | fields should be a dictionary. Keys as names of 63 | variables containing tuple (string comment, value). 64 | """ 65 | # Must use same dir to be truly atomic 66 | with AtomicFile(filename, 'w') as f: 67 | # write the values with comments. this is a silly comment 68 | for key in fields.keys(): 69 | f.write("# "+fields[key][0]+"\n") 70 | s = repr(fields[key][1]) 71 | f.write(key+"\t= ") 72 | if len(s) > 80: 73 | cut_string = "" 74 | while len(s) > 80: 75 | position = s.rfind(",",0,80)+1 76 | cut_string = cut_string + s[:position] + "\\\n\t\t" 77 | s = s[position:] 78 | s = cut_string + s 79 | f.write(s+"\n") 80 | 81 | class cfgset: 82 | def load(self, filename, defaults): 83 | """ 84 | Defaults should be key=variable name, value= 85 | tuple of (comment, default value) 86 | """ 87 | self._defaults = defaults 88 | self._filename = filename 89 | 90 | for i in defaults.keys(): 91 | self.__dict__[i] = defaults[i][1] 92 | 93 | # try to laad saved ones 94 | vars = _load_config(filename) 95 | if vars == None: 96 | # none found. this is new 97 | self.save() 98 | return 99 | for i in vars.keys(): 100 | self.__dict__[i] = vars[i] 101 | 102 | def save(self): 103 | """ 104 | Save borg settings 105 | """ 106 | keys = {} 107 | for i in self.__dict__.keys(): 108 | # reserved 109 | if i == "_defaults" or i == "_filename": 110 | continue 111 | if self._defaults.has_key(i): 112 | comment = self._defaults[i][0] 113 | else: 114 | comment = "" 115 | keys[i] = (comment, self.__dict__[i]) 116 | # save to config file 117 | _save_config(self._filename, keys) 118 | -------------------------------------------------------------------------------- /lib/irclib/testbot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # vim: set sw=4 sts=4 ts=8 et: 4 | # Example program using ircbot.py. 5 | # 6 | # Joel Rosdahl 7 | 8 | """A simple example bot. 9 | 10 | This is an example bot that uses the SingleServerIRCBot class from 11 | ircbot.py. The bot enters a channel and listens for commands in 12 | private messages and channel traffic. Commands in channel messages 13 | are given by prefixing the text by the bot name followed by a colon. 14 | It also responds to DCC CHAT invitations and echos data sent in such 15 | sessions. 16 | 17 | The known commands are: 18 | 19 | stats -- Prints some channel information. 20 | 21 | disconnect -- Disconnect the bot. The bot will try to reconnect 22 | after 60 seconds. 23 | 24 | die -- Let the bot cease to exist. 25 | 26 | dcc -- Let the bot invite you to a DCC CHAT connection. 27 | """ 28 | 29 | from ircbot import SingleServerIRCBot 30 | from irclib import nm_to_n, nm_to_h, irc_lower, ip_numstr_to_quad, ip_quad_to_numstr 31 | 32 | class TestBot(SingleServerIRCBot): 33 | def __init__(self, channel, nickname, server, port=6667): 34 | SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname) 35 | self.channel = channel 36 | 37 | def on_nicknameinuse(self, c, e): 38 | c.nick(c.get_nickname() + "_") 39 | 40 | def on_welcome(self, c, e): 41 | c.join(self.channel) 42 | 43 | def on_privmsg(self, c, e): 44 | self.do_command(e, e.arguments()[0]) 45 | 46 | def on_pubmsg(self, c, e): 47 | a = e.arguments()[0].split(":", 1) 48 | if len(a) > 1 and irc_lower(a[0]) == irc_lower(self.connection.get_nickname()): 49 | self.do_command(e, a[1].strip()) 50 | return 51 | 52 | def on_dccmsg(self, c, e): 53 | c.privmsg("You said: " + e.arguments()[0]) 54 | 55 | def on_dccchat(self, c, e): 56 | if len(e.arguments()) != 2: 57 | return 58 | args = e.arguments()[1].split() 59 | if len(args) == 4: 60 | try: 61 | address = ip_numstr_to_quad(args[2]) 62 | port = int(args[3]) 63 | except ValueError: 64 | return 65 | self.dcc_connect(address, port) 66 | 67 | def do_command(self, e, cmd): 68 | nick = nm_to_n(e.source()) 69 | c = self.connection 70 | 71 | if cmd == "disconnect": 72 | self.disconnect() 73 | elif cmd == "die": 74 | self.die() 75 | elif cmd == "stats": 76 | for chname, chobj in self.channels.items(): 77 | c.notice(nick, "--- Channel statistics ---") 78 | c.notice(nick, "Channel: " + chname) 79 | users = chobj.users() 80 | users.sort() 81 | c.notice(nick, "Users: " + ", ".join(users)) 82 | opers = chobj.opers() 83 | opers.sort() 84 | c.notice(nick, "Opers: " + ", ".join(opers)) 85 | voiced = chobj.voiced() 86 | voiced.sort() 87 | c.notice(nick, "Voiced: " + ", ".join(voiced)) 88 | elif cmd == "dcc": 89 | dcc = self.dcc_listen() 90 | c.ctcp("DCC", nick, "CHAT chat %s %d" % ( 91 | ip_quad_to_numstr(dcc.localaddress), 92 | dcc.localport)) 93 | else: 94 | c.notice(nick, "Not understood: " + cmd) 95 | 96 | def main(): 97 | import sys 98 | if len(sys.argv) != 4: 99 | print "Usage: testbot " 100 | sys.exit(1) 101 | 102 | s = sys.argv[1].split(":", 1) 103 | server = s[0] 104 | if len(s) == 2: 105 | try: 106 | port = int(s[1]) 107 | except ValueError: 108 | print "Error: Erroneous port." 109 | sys.exit(1) 110 | else: 111 | port = 6667 112 | channel = sys.argv[2] 113 | nickname = sys.argv[3] 114 | 115 | bot = TestBot(channel, nickname, server, port) 116 | bot.start() 117 | 118 | if __name__ == "__main__": 119 | main() 120 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg-telnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg Telnet module (fairly raw 'telnet'...) 5 | # Defaults to listening on port 8489 6 | # 7 | # Copyright (c) 2000, 2001 Tom Morton 8 | # 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation; either version 2 13 | # of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the Free Software 22 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 | # 24 | import sys 25 | import string 26 | import socket 27 | import SocketServer 28 | 29 | import pyborg 30 | 31 | class handler(SocketServer.BaseRequestHandler): 32 | # Command list for this module 33 | commandlist = "Telnet Module Commands:\n!quit" 34 | commanddict = {} 35 | 36 | def handle(self): 37 | #### 38 | if ("-v" in sys.argv) or ("--verbose" in sys.argv): 39 | self.opt_verbose = 1 40 | else: 41 | self.opt_verbose = 0 42 | #### 43 | print "Connection from ", self.request.getpeername() 44 | self.request.send("\r\nPyborg. Type !quit to leave.\r\n") 45 | while 1: 46 | try: 47 | self.request.send("> ") 48 | body = "" 49 | while 1: 50 | new = self.request.recv(1000) 51 | if new[-2:] != "\r\n": 52 | if new == '\x08': 53 | body = body[:-1] 54 | else: 55 | body = body + new 56 | else: 57 | body = body + new 58 | break 59 | except socket.error, e: 60 | print "Closed connection to", self.request.getpeername(), ": ", e, e.args 61 | return 62 | else: 63 | if self.opt_verbose: 64 | print "%s --> \"%s\"" % (self.request.getpeername(), body[:-2]) 65 | # Telnet module commands. 66 | if string.lower(body[0:5]) == "!quit": 67 | self.output("Bye", None) 68 | print "Closed connection to", self.request.getpeername(), ". User quit." 69 | return 70 | else: 71 | my_pyborg.process_msg(self, body, 100, 1, None, owner=0) 72 | 73 | def output(self, message, args): 74 | """ 75 | Output pyborg reply. 76 | """ 77 | if self.opt_verbose: 78 | print "%s <-- \"%s\"" % (self.request.getpeername(), message) 79 | try: 80 | self.request.send(message+"\r\n") 81 | except: 82 | pass 83 | 84 | if __name__ == '__main__': 85 | # start the damn server 86 | if "--help" in sys.argv: 87 | print "Pyborg telnet module." 88 | print 89 | print "-v --verbose" 90 | print "-p --port n Listen on port n (Defaults to 8489)" 91 | print 92 | sys.exit(0) 93 | 94 | port = 8489 95 | if "-p" in sys.argv or "--port" in sys.argv: 96 | try: 97 | x = sys.argv.index("-p") 98 | except ValueError, e: 99 | x = sys.argv.index("--port") 100 | if len(sys.argv) > x+1: 101 | try: 102 | port = int(sys.argv[x+1]) 103 | except ValueError, e: 104 | pass 105 | try: 106 | server = SocketServer.ThreadingTCPServer(("", port), handler) 107 | except socket.error, e: 108 | print "Socket error: ", e.args 109 | else: 110 | print "Starting pyborg..." 111 | my_pyborg = pyborg.pyborg() 112 | print "Awaiting connections..." 113 | try: 114 | server.serve_forever() 115 | except KeyboardInterrupt, e: 116 | print "Server shut down" 117 | my_pyborg.save_all() 118 | del my_pyborg 119 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/pyborg/ChangeLog: -------------------------------------------------------------------------------- 1 | PyBorg ChangeLog 2 | ~~~~~~~~~~~~~~~~ 3 | 4 | v1.1.1 - 19/9/2006 5 | ~~~~~~~~~~~~~~~~~~ 6 | 7 | * Added a tribune interface ( in the experimental directory ) 8 | 9 | * Added a regular expression system for aliases and censored list( exemple '~bisous': ['bi(s|z+)(ou|e?)(s*|x?)'] ) 10 | 11 | * Faster load 12 | 13 | * Added a no_save option 14 | 15 | ~~~~~~~~~~~~~~~~ 16 | 17 | v1.1.0 - 15/8/2006 18 | ~~~~~~~~~~~~~~~~~~ 19 | 20 | * Added a msn interface 21 | 22 | * Added a megahal interface 23 | 24 | * Rewritted the reply routine. Now PyBorg use a markov chain system. 25 | 26 | * Added a !talk command for make the bot talk to somebody on IRC 27 | 28 | * Added !purge command. It purge the dictionary for words who are very rare. 29 | 30 | * Added new filters for learn only real words ( no nicknames, no stranges 31 | wors ) 32 | 33 | * Save now the nicknames as '#nick' word, and replace it by the nickname of 34 | the target when speaking to somebody 35 | 36 | * Rewritted the censor routine. Now I am sure that the words won't be 37 | learned and won't be spoken 38 | 39 | * Added a file 'words.txt', containing all the words known. 40 | 41 | * New unlearn system, with a 'unlearn.txt' file, wich contain all the 42 | words that PyBorg has to unlearn. 43 | 44 | * Added a limit in the number of known words. With the !purge command and the 45 | unlearn.txt file, 6000-8000 words are now enought. 46 | 47 | * Bug fixed in the rebuilddict routine 48 | 49 | * Added a ignore list, wich contain the words wich have no importances in the 50 | sence of the sentece. 51 | 52 | * Added an alias system : the PyBorg can now associate words written 53 | diffrently ( i.e 'you' and 'u' ) 54 | 55 | * Added an !alias command for control the use of the aliases. 56 | 57 | * PyBorg continue to learn sentence, even if the learning setting is off 58 | ( but it will not learn any new words ) 59 | 60 | * Added a time pause for simulate a writting time 61 | 62 | * Save now all the .dat files in a zip file 63 | 64 | * The *.cfg files are now more easy to read, with multi-lines display 65 | 66 | ~~~~~~~~~~~~~~~~ 67 | 68 | v1.0.6 - 29/4/2002 69 | ~~~~~~~~~~~~~~~~~~ 70 | 71 | * Added !rebuilddict command. I suggest you run this command when using 72 | 1.0.6 for the first time with an old dictionary. 73 | 74 | * Added !checkdict command, which fixes broken links in the dictionary. 75 | 76 | * Speeded up the !unlearn command. It's a shitload faster now. A fucking 77 | metric shitload to be accurate. 78 | 79 | * Speeded up the learn routines. It's now also a greased up silent night 80 | necrophile load faster. 81 | 82 | v1.0.5 - 11/3/2002 83 | ~~~~~~~~~~~~~~~~~~ 84 | 85 | * (pyborg-irc) Don't need the borg.chans workaround anymore. 86 | 87 | * (pyborg-irc) Fixed crash in stealth mode when non-owner types '!'... 88 | 89 | v1.0.4 - 24/1/2002 90 | ~~~~~~~~~~~~~~~~~~ 91 | 92 | * (pyborg-irc) Catch exceptions and give the user a chance to save that 93 | morbidly large dictionary formed while hanging around on dalnet #sex... 94 | 95 | * Changed !learn and !nolearn to a single !learning [on|off] command. 96 | 97 | * The !help command now only lists the commands briefly, and !help 98 | gives a more in depth explaination of an individual command. 99 | 100 | * Output of !help sent to remote user again, since it is smaller now and 101 | won't get the bot killed for flooding. 102 | 103 | * (pyborg-telnet) Fixed to play properly with that strange windows telnet 104 | client (provided it [windows telnet] is in local echo mode). 105 | 106 | * (pyborg-irc) Added a configure option and command 'quitmsg', to set 107 | the pyborg quit message. We now properly quit from irc sessions. 108 | 109 | * (pyborg-irc) Pyborg listens to CTCP ACTIONs again (broken since moving 110 | to irclib 0.3.x) 111 | 112 | v1.0.3 - 10/11/2001 113 | ~~~~~~~~~~~~~~~~~~~ 114 | 115 | * (pyborg-irc) Stealth mode (!stealth on|off) to disable non owner 116 | commands and lie about our CTCP version. 117 | 118 | * Fixed stupid bug where the bot copied people exactly. 119 | 120 | * Changed to official irclib 0.3.2, which has the fixes mentioned 121 | in changes for 1.0.2. 122 | 123 | * Owner command security fix (thanks #q3f people) 124 | 125 | v1.0.2 - 18/7/2001 126 | ~~~~~~~~~~~~~~~~~~ 127 | 128 | * Fixed bug which made dictionaries saved in win32 and in unix 129 | incompatible. 130 | 131 | * Fixed bug in irclib 0.3.1 where removing channel user limits 132 | crashes the bot. 133 | 134 | * Fixed bug in irclib 0.3.1 where joining a channel with a capital 135 | W in the name crashes the bot. 136 | 137 | v1.0.1 - 19/6/2001 138 | ~~~~~~~~~~~~~~~~~~ 139 | 140 | * (pyborg-irc) Changed to use irclib 0.3.1 141 | 142 | v1.0.0 - 22/5/2001 143 | ~~~~~~~~~~~~~~~~~~ 144 | 145 | Must use convert.py on pyborg 0.9.10 and 0.9.11 dictionaries. 146 | 147 | * Faster dictionary load/save and slightly lower memory usage 148 | 149 | * (pyborg-irc) On join and kick replies now obey reply rate. 150 | 151 | * (pyborg-irc) Optional mIRC colour filtering 152 | 153 | * (pyborg-irc) Optional censoring of words. 154 | 155 | * (pyborg-irc) Double chance of replying to messages containing own nickname 156 | 157 | * (pyborg-telnet) Telnet server module. 158 | 159 | * Lots of other stuff i've forgotten 160 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg-msnp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg MSN module 5 | # 6 | # Copyright (c) 2006 Sebastien Dailly 7 | # 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | 24 | 25 | 26 | import time 27 | import sys 28 | import pyborg 29 | import cfgfile 30 | import traceback 31 | import thread 32 | try: 33 | import msnp 34 | except: 35 | print "ERROR !!!!\msnp not found, please install it (http://msnp.sourceforge.net/)" 36 | sys.exit(1) 37 | 38 | def get_time(): 39 | """ 40 | Return time as a nice yummy string 41 | """ 42 | return time.strftime("%H:%M:%S", time.localtime(time.time())) 43 | 44 | 45 | class ModMSN(msnp.Session, msnp.ChatCallbacks): 46 | 47 | def __init__(self, my_pyborg, args): 48 | """ 49 | Args will be sys.argv (command prompt arguments) 50 | """ 51 | # PyBorg 52 | self.pyborg = my_pyborg 53 | # load settings 54 | 55 | self.settings = cfgfile.cfgset() 56 | self.settings.load("pyborg-msn.cfg", 57 | { "myname": ("The bot's nickname", "PyBorg"), 58 | "msn_passport": ("Reported passport account", "passport@hotmail.com"), 59 | "msn_password": ("Reported password account", "password"), 60 | "owners": ("Owner(s) passport account", [ "owner@hotmail.com" ]), 61 | "password": ("password for control the bot (Edit manually !)", "") 62 | }) 63 | 64 | 65 | self.owners = self.settings.owners[:] 66 | 67 | def our_start(self): 68 | print "Connecting to msn..." 69 | msnp.Session.__init__(self, self.MsnListener(self)) 70 | self.login(self.settings.msn_passport, self.settings.msn_password) 71 | 72 | if self.logged_in: print "connected" 73 | self.sync_friend_list() 74 | while True: 75 | bot.process(chats = True) 76 | time.sleep(1) 77 | 78 | class MsnListener(msnp.SessionCallbacks): 79 | 80 | def __init__(self, bot): 81 | self.bot = bot 82 | 83 | def chat_started(self, chat): 84 | callbacks = ModMSN.MsnChatActions(bot) 85 | chat.callbacks = callbacks 86 | callbacks.chat = chat 87 | 88 | 89 | class MsnChatActions(msnp.ChatCallbacks): 90 | 91 | # Command list for this module 92 | commandlist = "MSN Module Commands:\n!nick, !owner" 93 | 94 | # Detailed command description dictionary 95 | commanddict = { 96 | "nick": "Owner command. Usage: !nick nickname\nChange nickname", 97 | "quit": "Owner command. Usage: !quit\nMake the bot quit IRC", 98 | "owner": "Usage: !owner password\nAllow to become owner of the bot" 99 | } 100 | 101 | 102 | def __init__(self, bot): 103 | self.bot = bot 104 | 105 | def message_received(self, passport_id, display_name, text, charset): 106 | print '%s: %s' % (passport_id, text) 107 | if text[0] == '!': 108 | if self.msn_command(passport_id, display_name, text, charset) == 1: 109 | return 110 | 111 | self.chat.send_typing() 112 | if passport_id in bot.owners: 113 | bot.pyborg.process_msg(self, text, 100, 1, (charset, display_name, text), owner=1) 114 | else: 115 | thread.start_new_thread(bot.pyborg.process_msg, (self, text, 100, 1, (charset, display_name, text))) 116 | 117 | def msn_command(self, passport_id, display_name, text, charset): 118 | command_list = text.split() 119 | command_list[0] = command_list[0].lower() 120 | 121 | if command_list[0] == "!owner" and len(command_list) > 1 and passport_id not in bot.owners: 122 | if command_list[1] == bot.settings.password: 123 | bot.owners.append(passport_id) 124 | self.output("You've been added to owners list", (charset, display_name, text)) 125 | else: 126 | self.output("try again", (charset)) 127 | 128 | 129 | if passport_id in bot.owners: 130 | if command_list[0] == '!nick' and len(command_list) > 1: 131 | bot.change_display_name(command_list[1]) 132 | 133 | def output(self, message, args): 134 | charset, display_name, text = args 135 | 136 | message = message.replace("#nick", display_name) 137 | 138 | print "[%s] <%s> > %s> %s" % (get_time(), display_name, bot.display_name, text) 139 | print "[%s] <%s> > %s> %s" % (get_time(), bot.display_name, display_name, message) 140 | self.chat.send_message(message, charset) 141 | 142 | 143 | 144 | if __name__ == "__main__": 145 | 146 | if "--help" in sys.argv: 147 | print "Pyborg msn bot. Usage:" 148 | print " pyborg-msn.py" 149 | print "Defaults stored in pyborg-msn.cfg" 150 | print 151 | sys.exit(0) 152 | # start the pyborg 153 | my_pyborg = pyborg.pyborg() 154 | bot = ModMSN(my_pyborg, sys.argv) 155 | try: 156 | bot.our_start() 157 | 158 | except KeyboardInterrupt, e: 159 | pass 160 | except SystemExit, e: 161 | pass 162 | except: 163 | traceback.print_exc() 164 | c = raw_input("Ooops! It looks like Pyborg has crashed. Would you like to save its dictionary? (y/n) ") 165 | if c.lower()[:1] == 'n': 166 | sys.exit(0) 167 | bot.logout() 168 | my_pyborg.save_all() 169 | del my_pyborg 170 | -------------------------------------------------------------------------------- /lib/pyborg/readme: -------------------------------------------------------------------------------- 1 | # 2 | # PyBorg: The python AI bot. 3 | # 4 | # Copyright (c) 2000, 2006 Tom Morton, Sébastien Dailly 5 | # 6 | # This bot was inspired by the PerlBorg, by Eric Bock. 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 11 | # of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 | # 22 | # Tom Morton 23 | # 24 | 25 | PyBorg 1.1.0 needs Python 1.5.2 or greater (www.python.org). 26 | The 'irclib' and 'ircbot' modules in the PyBorg archive were written by Joel 27 | Rosdahl (www.rosdahl.net). PyBorg has been tested on linux, windows and BSD 28 | and /may/ work on other Python platforms. 29 | 30 | 'python pyborg-linein.py' to chat with the bot offline. 31 | 'python pyborg-irc.py' for irc mode. 32 | 33 | Use convert2.py to convert pyborg olders dictionaries to pyborg 1.1.0 34 | format. The dictionary is stored in lines.dat and words.dat and saved in a zip 35 | file. 36 | 37 | Orders : 38 | -------- 39 | 40 | All the orders start with one “!”. According to whether the bot is connected to 41 | IRC or not, the list of the order is not the same one. Here the list of the 42 | orders which are usable all the time: 43 | 44 | * !help: post the whole of the orders available 45 | * !version: post the version of the bot 46 | * !quit: leave the program 47 | * !jump: reconnect to IRC 48 | * !save: safeguard the dictionary and the files of configuration 49 | 50 | * !words: post the number of words and sentences known 51 | * !known [Word]: post if the word [Word] is known and numbers it sentences 52 | in which it appears 53 | 54 | * !unlearn [Word]: erase the word of the dictionary 55 | * !purge: Post the number of words which appear only in one context 56 | * !purge [a number]: erase [a number] words of the dictionary, among the 57 | least frequent words 58 | * !replace [word1] [word2]: replace all the events of [word1] by [word2] 59 | * !censor [Word]: censure the word [Word] 60 | * !uncensor [Word]: withdraw [Word] list of censure 61 | 62 | * !learning [on|off]: authorize or not the training of new words 63 | * !limit [a number]: limit the number of words known with [a number] 64 | (by defect 6000) 65 | 66 | * !alias: List all alias what exists 67 | * !alias [alias]: List all the words which refer to {alias] 68 | * !alias [alias] [word1] [wordN]: create alias which replaces all the events 69 | of [Word] by [alias] 70 | 71 | The following orders are available only on IRC: 72 | 73 | * !nick [nick]: change nick the bot 74 | * !shutup: prevent the bot from speaking on the channels 75 | * !wakeup: cancel one! shutup 76 | * !replyrate [a number]: probability that the bot answers a message diffused 77 | on the channel 78 | * !talk [nick] [message]: send the message [message] with [nick] on behalf 79 | of the bot 80 | 81 | * !join [channel]: connect the bot to [channel] 82 | * !leaves [channel]: fact of leaving the channel [channel] 83 | * !chans: list the channels where the bot is connected 84 | 85 | * !ignore of [nick]: ignore nick 86 | * !uningore [nick]: withdraw nick list of the people to be ignored 87 | 88 | * !owner password: allows to be added to the list of the owners (the 89 | password is defined in the file pyborg-irc.cfg) 90 | 91 | Configuration files : 92 | --------------------- 93 | 94 | Edit pyborg.cfg, and pyborg-irc.cfg (created the first time you run pyborg-irc) 95 | to configure the bot. 96 | 97 | pyborg.cfg: 98 | 99 | * num_aliases: variable of the program, not to change, indicates the number 100 | of known alias 101 | * num_contexts: variable of the program, not to change, indicates the number 102 | of known sentences 103 | * ignore_list: indicate the list of words which are not relevant in a 104 | sentence (ex: [“one”, “a”, “of”, “some”] 105 | * max_words: maximum limit with the number of known words, can be changed 106 | thanks to the order !limit 107 | * learning: indicate if the bot must learn or not. Can be changed thanks to 108 | the order !learning 109 | * aliases: the list of alias. Can be changed with the order! alias 110 | * censored: the list of the censured words. Can be changed with the order 111 | !censor !uncensor 112 | * num_words: variable of the program, not to change, indicates the number of 113 | known words 114 | * no_save: if True, the program will not do any saves on disk. 115 | 116 | pyborg-irc.cfg: 117 | 118 | * owners: a list of owners of the bot 119 | * reply_chance: percentage of chance that the bot answers a message diffused 120 | on the channel. Can be changed with the order! replyrate (see the order! 121 | replyrate) 122 | * reply_to_ignored: 0 or 1 make it possible to answer or not the people who 123 | in the list of are ignored 124 | * chans: a list channels one where the bot must be connected (is not 125 | modified by the order !join) 126 | * servers: a list of waiters where the bot must be connected 127 | * ignorelist: a list of people which the bot will not answer (see the order! 128 | be unaware of! unignore) 129 | * quit_message: message of exit to the disconnection 130 | * password: password for the order! owner 131 | * nickserv: auth the bot, e.g. ("name", "password") 132 | * !speakin: 0 or 1 indicate if the bot must chatter on the channels, can be 133 | changed with the orders! shutup! wakeup 134 | 135 | The aliases and censored words are regular expression. This mean that you can 136 | set an aliases like '~hello': ['hell?o'] and each time pyborg will read 'hello' 137 | or 'helo', it will replace the world by hello. The '~' as now role for now, but 138 | says to pyborg that the word is an alias and can be used in the future. 139 | 140 | NOTE: Terminate the borg with the !quit command or CTRL-C in the 141 | console. Do not simply close the console window or the dictionary 142 | will not be saved. 143 | 144 | -- 145 | Tom Morton 146 | Sébastien Dailly 147 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | # 2 | # PyBorg: The python AI bot. 3 | # 4 | # Copyright (c) 2000, 2006 Tom Morton, Sébastien Dailly 5 | # 6 | # This bot was inspired by the PerlBorg, by Eric Bock. 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 11 | # of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 | # 22 | # Tom Morton 23 | # 24 | 25 | 26 | ############################################################################# 27 | 28 | This version has been customized so all data and configuration is stored into data/ 29 | To startup just run './start.sh' 30 | 31 | ############################################################################# 32 | 33 | 34 | PyBorg 1.1.0 needs Python 1.5.2 or greater (www.python.org). 35 | The 'irclib' and 'ircbot' modules in the PyBorg archive were written by Joel 36 | Rosdahl (www.rosdahl.net). PyBorg has been tested on linux, windows and BSD 37 | and /may/ work on other Python platforms. 38 | 39 | 'python pyborg-linein.py' to chat with the bot offline. 40 | 'python pyborg-irc.py' for irc mode. 41 | 42 | Use convert2.py to convert pyborg olders dictionaries to pyborg 1.1.0 43 | format. The dictionary is stored in lines.dat and words.dat and saved in a zip 44 | file. 45 | 46 | Orders : 47 | -------- 48 | 49 | All the orders start with one “!”. According to whether the bot is connected to 50 | IRC or not, the list of the order is not the same one. Here the list of the 51 | orders which are usable all the time: 52 | 53 | * !help: post the whole of the orders available 54 | * !version: post the version of the bot 55 | * !quit: leave the program 56 | * !save: safeguard the dictionary and the files of configuration 57 | 58 | * !words: post the number of words and sentences known 59 | * !known [Word]: post if the word [Word] is known and numbers it sentences 60 | in which it appears 61 | 62 | * !unlearn [Word]: erase the word of the dictionary 63 | * !purge: Post the number of words which appear only in one context 64 | * !purge [a number]: erase [a number] words of the dictionary, among the 65 | least frequent words 66 | * !replace [word1] [word2]: replace all the events of [word1] by [word2] 67 | * !censor [Word]: censure the word [Word] 68 | * !uncensor [Word]: withdraw [Word] list of censure 69 | 70 | * !learning [on|off]: authorize or not the training of new words 71 | * !limit [a number]: limit the number of words known with [a number] 72 | (by defect 6000) 73 | 74 | * !alias: List all alias what exists 75 | * !alias [alias]: List all the words which refer to {alias] 76 | * !alias [alias] [word1] [wordN]: create alias which replaces all the events 77 | of [Word] by [alias] 78 | 79 | The following orders are available only on IRC: 80 | 81 | * !nick [nick]: change nick the bot 82 | * !shutup: prevent the bot from speaking on the channels 83 | * !wakeup: cancel one! shutup 84 | * !replyrate [a number]: probability that the bot answers a message diffused 85 | on the channel 86 | * !talk [nick] [message]: send the message [message] with [nick] on behalf 87 | of the bot 88 | 89 | * !join [channel]: connect the bot to [channel] 90 | * !leaves [channel]: fact of leaving the channel [channel] 91 | * !chans: list the channels where the bot is connected 92 | 93 | * !ignore of [nick]: ignore nick 94 | * !unignore [nick]: withdraw nick list of the people to be ignored 95 | 96 | * !owner password: allows to be added to the list of the owners (the 97 | password is defined in the file pyborg-irc.cfg) 98 | 99 | Configuration files : 100 | --------------------- 101 | 102 | Edit pyborg.cfg, and pyborg-irc.cfg (created the first time you run pyborg-irc) 103 | to configure the bot. 104 | 105 | pyborg.cfg: 106 | 107 | * num_aliases: variable of the program, not to change, indicates the number 108 | of known alias 109 | * num_contexts: variable of the program, not to change, indicates the number 110 | of known sentences 111 | * ignore_list: indicate the list of words which are not relevant in a 112 | sentence (ex: [“one”, “a”, “of”, “some”] 113 | * max_words: maximum limit with the number of known words, can be changed 114 | thanks to the order !limit 115 | * learning: indicate if the bot must learn or not. Can be changed thanks to 116 | the order !learning 117 | * aliases: the list of alias. Can be changed with the order! alias 118 | * censored: the list of the censured words. Can be changed with the order 119 | !censor !uncensor 120 | * num_words: variable of the program, not to change, indicates the number of 121 | known words 122 | * no_save: if True, the program will not do any saves on disk. 123 | 124 | pyborg-irc.cfg: 125 | 126 | * owners: a list of owners of the bot 127 | * reply_chance: percentage of chance that the bot answers a message diffused 128 | on the channel. Can be changed with the order! replyrate (see the order! 129 | replyrate) 130 | * reply_to_ignored: 0 or 1 make it possible to answer or not the people who 131 | in the list of are ignored 132 | * chans: a list channels one where the bot must be connected (is not 133 | modified by the order !join) 134 | * servers: a list of waiters where the bot must be connected 135 | * ignorelist: a list of people which the bot will not answer (see the order! 136 | be unaware of! unignore) 137 | * quit_message: message of exit to the disconnection 138 | * password: password for the order! owner 139 | * !speakin: 0 or 1 indicate if the bot must chatter on the channels, can be 140 | changed with the orders! shutup! wakeup 141 | 142 | The aliases and censored words are regular expression. This mean that you can 143 | set an aliases like '~hello': ['hell?o'] and each time pyborg will read 'hello' 144 | or 'helo', it will replace the world by hello. The '~' as now role for now, but 145 | says to pyborg that the word is an alias and can be used in the future. 146 | 147 | NOTE: Terminate the borg with the !quit command or CTRL-C in the 148 | console. Do not simply close the console window or the dictionary 149 | will not be saved. 150 | 151 | -- 152 | Tom Morton 153 | Sébastien Dailly 154 | -------------------------------------------------------------------------------- /lib/irclib/ircbot.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 1999--2002 Joel Rosdahl 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | # 18 | # Joel Rosdahl 19 | # 20 | # $Id: ircbot.py,v 1.23 2008/09/11 07:38:30 keltus Exp $ 21 | 22 | """ircbot -- Simple IRC bot library. 23 | 24 | This module contains a single-server IRC bot class that can be used to 25 | write simpler bots. 26 | """ 27 | 28 | import sys 29 | from UserDict import UserDict 30 | 31 | from irclib import SimpleIRCClient 32 | from irclib import nm_to_n, irc_lower, all_events 33 | from irclib import parse_channel_modes, is_channel 34 | from irclib import ServerConnectionError 35 | 36 | class SingleServerIRCBot(SimpleIRCClient): 37 | """A single-server IRC bot class. 38 | 39 | The bot tries to reconnect if it is disconnected. 40 | 41 | The bot keeps track of the channels it has joined, the other 42 | clients that are present in the channels and which of those that 43 | have operator or voice modes. The "database" is kept in the 44 | self.channels attribute, which is an IRCDict of Channels. 45 | """ 46 | def __init__(self, server_list, nickname, realname, 47 | reconnection_interval=60, localaddress="", ipv6=0, ssl=0): 48 | """Constructor for SingleServerIRCBot objects. 49 | 50 | Arguments: 51 | 52 | server_list -- A list of tuples (server, port) that 53 | defines which servers the bot should try to 54 | connect to. 55 | 56 | nickname -- The bot's nickname. 57 | 58 | realname -- The bot's realname. 59 | 60 | reconnection_interval -- How long the bot should wait 61 | before trying to reconnect. 62 | 63 | dcc_connections -- A list of initiated/accepted DCC 64 | connections. 65 | """ 66 | 67 | SimpleIRCClient.__init__(self) 68 | self.channels = IRCDict() 69 | self.server_list = server_list 70 | if not reconnection_interval or reconnection_interval < 0: 71 | reconnection_interval = 2**31 72 | self.reconnection_interval = reconnection_interval 73 | 74 | self._nickname = nickname 75 | self._realname = realname 76 | self._localaddress = localaddress 77 | self._ipv6 = ipv6 78 | self._ssl = ssl 79 | for i in ["disconnect", "join", "kick", "mode", 80 | "namreply", "nick", "part", "quit"]: 81 | self.connection.add_global_handler(i, 82 | getattr(self, "_on_" + i), 83 | -10) 84 | def _connected_checker(self): 85 | """[Internal]""" 86 | if not self.connection.is_connected(): 87 | self.connection.execute_delayed(self.reconnection_interval, 88 | self._connected_checker) 89 | self.jump_server() 90 | 91 | def _connect(self): 92 | """[Internal]""" 93 | password = None 94 | if len(self.server_list[0]) > 2: 95 | password = self.server_list[0][2] 96 | try: 97 | print("Connecting to %s:%d" % (self.server_list[0][0], 98 | self.server_list[0][1])) 99 | self.connect(self.server_list[0][0], 100 | self.server_list[0][1], 101 | self._nickname, 102 | password, 103 | ircname=self._realname, 104 | localaddress=self._localaddress, 105 | ssl=self._ssl, 106 | ipv6=self._ipv6) 107 | except ServerConnectionError: 108 | pass 109 | 110 | def _on_disconnect(self, c, e): 111 | """[Internal]""" 112 | self.channels = IRCDict() 113 | self.connection.execute_delayed(self.reconnection_interval, 114 | self._connected_checker) 115 | 116 | def _on_join(self, c, e): 117 | """[Internal]""" 118 | ch = e.target() 119 | nick = nm_to_n(e.source()) 120 | if nick == c.get_nickname(): 121 | self.channels[ch] = Channel() 122 | self.channels[ch].add_user(nick) 123 | 124 | def _on_kick(self, c, e): 125 | """[Internal]""" 126 | nick = e.arguments()[0] 127 | channel = e.target() 128 | 129 | if nick == c.get_nickname(): 130 | del self.channels[channel] 131 | else: 132 | self.channels[channel].remove_user(nick) 133 | 134 | def _on_mode(self, c, e): 135 | """[Internal]""" 136 | modes = parse_channel_modes(" ".join(e.arguments())) 137 | t = e.target() 138 | if is_channel(t): 139 | ch = self.channels[t] 140 | for mode in modes: 141 | if mode[0] == "+": 142 | f = ch.set_mode 143 | else: 144 | f = ch.clear_mode 145 | f(mode[1], mode[2]) 146 | else: 147 | # Mode on self... XXX 148 | pass 149 | 150 | def _on_namreply(self, c, e): 151 | """[Internal]""" 152 | 153 | # e.arguments()[0] == "@" for secret channels, 154 | # "*" for private channels, 155 | # "=" for others (public channels) 156 | # e.arguments()[1] == channel 157 | # e.arguments()[2] == nick list 158 | 159 | ch = e.arguments()[1] 160 | for nick in e.arguments()[2].split(): 161 | if nick[0] == "@": 162 | nick = nick[1:] 163 | self.channels[ch].set_mode("o", nick) 164 | elif nick[0] == "+": 165 | nick = nick[1:] 166 | self.channels[ch].set_mode("v", nick) 167 | self.channels[ch].add_user(nick) 168 | 169 | def _on_nick(self, c, e): 170 | """[Internal]""" 171 | before = nm_to_n(e.source()) 172 | after = e.target() 173 | for ch in self.channels.values(): 174 | if ch.has_user(before): 175 | ch.change_nick(before, after) 176 | 177 | def _on_part(self, c, e): 178 | """[Internal]""" 179 | nick = nm_to_n(e.source()) 180 | channel = e.target() 181 | 182 | if nick == c.get_nickname(): 183 | del self.channels[channel] 184 | else: 185 | self.channels[channel].remove_user(nick) 186 | 187 | def _on_quit(self, c, e): 188 | """[Internal]""" 189 | nick = nm_to_n(e.source()) 190 | for ch in self.channels.values(): 191 | if ch.has_user(nick): 192 | ch.remove_user(nick) 193 | 194 | def die(self, msg="Bye, cruel world!"): 195 | """Let the bot die. 196 | 197 | Arguments: 198 | 199 | msg -- Quit message. 200 | """ 201 | 202 | self.connection.disconnect(msg) 203 | sys.exit(0) 204 | 205 | def disconnect(self, msg="I'll be back!"): 206 | """Disconnect the bot. 207 | 208 | The bot will try to reconnect after a while. 209 | 210 | Arguments: 211 | 212 | msg -- Quit message. 213 | """ 214 | self.connection.disconnect(msg) 215 | 216 | def get_version(self): 217 | """Returns the bot version. 218 | 219 | Used when answering a CTCP VERSION request. 220 | """ 221 | return "ircbot.py by Joel Rosdahl " 222 | 223 | def jump_server(self, msg="Changing servers"): 224 | """Connect to a new server, possibly disconnecting from the current. 225 | 226 | The bot will skip to next server in the server_list each time 227 | jump_server is called. 228 | """ 229 | if self.connection.is_connected(): 230 | self.connection.disconnect(msg) 231 | 232 | self.server_list.append(self.server_list.pop(0)) 233 | self._connect() 234 | 235 | def on_ctcp(self, c, e): 236 | """Default handler for ctcp events. 237 | 238 | Replies to VERSION and PING requests and relays DCC requests 239 | to the on_dccchat method. 240 | """ 241 | if e.arguments()[0] == "VERSION": 242 | c.ctcp_reply(nm_to_n(e.source()), 243 | "VERSION " + self.get_version()) 244 | elif e.arguments()[0] == "PING": 245 | if len(e.arguments()) > 1: 246 | c.ctcp_reply(nm_to_n(e.source()), 247 | "PING " + e.arguments()[1]) 248 | elif e.arguments()[0] == "DCC" and e.arguments()[1].split(" ", 1)[0] == "CHAT": 249 | self.on_dccchat(c, e) 250 | 251 | def on_dccchat(self, c, e): 252 | pass 253 | 254 | def start(self): 255 | """Start the bot.""" 256 | self._connect() 257 | SimpleIRCClient.start(self) 258 | 259 | 260 | class IRCDict: 261 | """A dictionary suitable for storing IRC-related things. 262 | 263 | Dictionary keys a and b are considered equal if and only if 264 | irc_lower(a) == irc_lower(b) 265 | 266 | Otherwise, it should behave exactly as a normal dictionary. 267 | """ 268 | 269 | def __init__(self, dict=None): 270 | self.data = {} 271 | self.canon_keys = {} # Canonical keys 272 | if dict is not None: 273 | self.update(dict) 274 | def __repr__(self): 275 | return repr(self.data) 276 | def __cmp__(self, dict): 277 | if isinstance(dict, IRCDict): 278 | return cmp(self.data, dict.data) 279 | else: 280 | return cmp(self.data, dict) 281 | def __len__(self): 282 | return len(self.data) 283 | def __getitem__(self, key): 284 | return self.data[self.canon_keys[irc_lower(key)]] 285 | def __setitem__(self, key, item): 286 | if key in self: 287 | del self[key] 288 | self.data[key] = item 289 | self.canon_keys[irc_lower(key)] = key 290 | def __delitem__(self, key): 291 | ck = irc_lower(key) 292 | del self.data[self.canon_keys[ck]] 293 | del self.canon_keys[ck] 294 | def __iter__(self): 295 | return iter(self.data) 296 | def __contains__(self, key): 297 | return self.has_key(key) 298 | def clear(self): 299 | self.data.clear() 300 | self.canon_keys.clear() 301 | def copy(self): 302 | if self.__class__ is UserDict: 303 | return UserDict(self.data) 304 | import copy 305 | return copy.copy(self) 306 | def keys(self): 307 | return self.data.keys() 308 | def items(self): 309 | return self.data.items() 310 | def values(self): 311 | return self.data.values() 312 | def has_key(self, key): 313 | return irc_lower(key) in self.canon_keys 314 | def update(self, dict): 315 | for k, v in dict.items(): 316 | self.data[k] = v 317 | def get(self, key, failobj=None): 318 | return self.data.get(key, failobj) 319 | 320 | 321 | class Channel: 322 | """A class for keeping information about an IRC channel. 323 | 324 | This class can be improved a lot. 325 | """ 326 | 327 | def __init__(self): 328 | self.userdict = IRCDict() 329 | self.operdict = IRCDict() 330 | self.voiceddict = IRCDict() 331 | self.modes = {} 332 | 333 | def users(self): 334 | """Returns an unsorted list of the channel's users.""" 335 | return self.userdict.keys() 336 | 337 | def opers(self): 338 | """Returns an unsorted list of the channel's operators.""" 339 | return self.operdict.keys() 340 | 341 | def voiced(self): 342 | """Returns an unsorted list of the persons that have voice 343 | mode set in the channel.""" 344 | return self.voiceddict.keys() 345 | 346 | def has_user(self, nick): 347 | """Check whether the channel has a user.""" 348 | return nick in self.userdict 349 | 350 | def is_oper(self, nick): 351 | """Check whether a user has operator status in the channel.""" 352 | return nick in self.operdict 353 | 354 | def is_voiced(self, nick): 355 | """Check whether a user has voice mode set in the channel.""" 356 | return nick in self.voiceddict 357 | 358 | def add_user(self, nick): 359 | self.userdict[nick] = 1 360 | 361 | def remove_user(self, nick): 362 | for d in self.userdict, self.operdict, self.voiceddict: 363 | if nick in d: 364 | del d[nick] 365 | 366 | def change_nick(self, before, after): 367 | self.userdict[after] = 1 368 | del self.userdict[before] 369 | if before in self.operdict: 370 | self.operdict[after] = 1 371 | del self.operdict[before] 372 | if before in self.voiceddict: 373 | self.voiceddict[after] = 1 374 | del self.voiceddict[before] 375 | 376 | def set_mode(self, mode, value=None): 377 | """Set mode on the channel. 378 | 379 | Arguments: 380 | 381 | mode -- The mode (a single-character string). 382 | 383 | value -- Value 384 | """ 385 | if mode == "o": 386 | self.operdict[value] = 1 387 | elif mode == "v": 388 | self.voiceddict[value] = 1 389 | else: 390 | self.modes[mode] = value 391 | 392 | def clear_mode(self, mode, value=None): 393 | """Clear mode on the channel. 394 | 395 | Arguments: 396 | 397 | mode -- The mode (a single-character string). 398 | 399 | value -- Value 400 | """ 401 | try: 402 | if mode == "o": 403 | del self.operdict[value] 404 | elif mode == "v": 405 | del self.voiceddict[value] 406 | else: 407 | del self.modes[mode] 408 | except KeyError: 409 | pass 410 | 411 | def has_mode(self, mode): 412 | return mode in self.modes 413 | 414 | def is_moderated(self): 415 | return self.has_mode("m") 416 | 417 | def is_secret(self): 418 | return self.has_mode("s") 419 | 420 | def is_protected(self): 421 | return self.has_mode("p") 422 | 423 | def has_topic_lock(self): 424 | return self.has_mode("t") 425 | 426 | def is_invite_only(self): 427 | return self.has_mode("i") 428 | 429 | def has_allow_external_messages(self): 430 | return self.has_mode("n") 431 | 432 | def has_limit(self): 433 | return self.has_mode("l") 434 | 435 | def limit(self): 436 | if self.has_limit(): 437 | return self.modes[l] 438 | else: 439 | return None 440 | 441 | def has_key(self): 442 | return self.has_mode("k") 443 | 444 | def key(self): 445 | if self.has_key(): 446 | return self.modes["k"] 447 | else: 448 | return None 449 | -------------------------------------------------------------------------------- /lib/pyborg/copying.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /lib/irclib/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 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg-irc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | # 4 | # PyBorg IRC module 5 | # 6 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 7 | # 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | 24 | import errno, signal, sys, time 25 | from threading import Timer 26 | 27 | try: 28 | from ircbot import * 29 | from irclib import * 30 | except: 31 | print "ERROR !!!!\nircbot.py and irclib.py not found, please install them\n(http://python-irclib.sourceforge.net/)" 32 | sys.exit(1) 33 | 34 | #overide irclib function 35 | def my_remove_connection(self, connection): 36 | # XXX: Why is this here? 37 | #self.connections.remove(connection) 38 | if self.fn_to_remove_socket: 39 | self.fn_to_remove_socket(connection._get_socket()) 40 | 41 | IRC._remove_connection = my_remove_connection 42 | 43 | import os 44 | import pyborg 45 | import cfgfile 46 | import random 47 | import time 48 | import traceback 49 | import thread 50 | 51 | def sigterm_handler(_signo, _stack_frame): 52 | devnull = os.open(os.devnull, os.O_WRONLY) 53 | os.dup2(devnull, sys.stdout.fileno()) 54 | 55 | # Raises SystemExit(0): 56 | sys.exit(0) 57 | 58 | def get_time(): 59 | """ 60 | Return time as a nice yummy string 61 | """ 62 | return time.strftime("%H:%M:%S", time.localtime(time.time())) 63 | 64 | 65 | class ModIRC(SingleServerIRCBot): 66 | """ 67 | Module to interface IRC input and output with the PyBorg learn 68 | and reply modules. 69 | """ 70 | # The bot recieves a standard message on join. The standard part 71 | # message is only used if the user doesn't have a part message. 72 | join_msg = "%s"# is here" 73 | part_msg = "%s"# has left" 74 | 75 | # For security the owner's host mask is stored 76 | # DON'T CHANGE THIS 77 | owner_mask = [] 78 | 79 | 80 | # Command list for this module 81 | commandlist = "IRC Module Commands:\n!chans, !ignore, \ 82 | !join, !nick, !part, !quit, !quitmsg, !jump, !reply2ignored, !replyrate, !shutup, \ 83 | !stealth, !unignore, !wakeup, !talk, !me, !owner" 84 | # Detailed command description dictionary 85 | commanddict = { 86 | "shutup": "Owner command. Usage: !shutup\nStop the bot talking", 87 | "wakeup": "Owner command. Usage: !wakeup\nAllow the bot to talk", 88 | "join": "Owner command. Usage: !join #chan1 [#chan2 [...]]\nJoin one or more channels", 89 | "part": "Owner command. Usage: !part #chan1 [#chan2 [...]]\nLeave one or more channels", 90 | "chans": "Owner command. Usage: !chans\nList channels currently on", 91 | "nick": "Owner command. Usage: !nick nickname\nChange nickname", 92 | "ignore": "Owner command. Usage: !ignore [nick1 [nick2 [...]]]\nIgnore one or more nicknames. Without arguments it lists ignored nicknames", 93 | "unignore": "Owner command. Usage: !unignore nick1 [nick2 [...]]\nUnignores one or more nicknames", 94 | "replyrate": "Owner command. Usage: !replyrate [rate%]\nSet rate of bot replies to rate%. Without arguments (not an owner-only command) shows the current reply rate", 95 | "reply2ignored": "Owner command. Usage: !reply2ignored [on|off]\nAllow/disallow replying to ignored users. Without arguments shows the current setting", 96 | "stealth": "Owner command. Usage: !stealth [on|off]\nTurn stealth mode on or off (disable non-owner commands and don't return CTCP VERSION). Without arguments shows the current setting", 97 | "quitmsg": "Owner command. Usage: !quitmsg [message]\nSet the quit message. Without arguments show the current quit message", 98 | "talk": "Owner command. Usage !talk nick message\nmake the bot send the sentence 'message' to 'nick'", 99 | "me": "Owner command. Usage !me nick message\nmake the bot send the sentence 'message' to 'nick'", 100 | "jump": "Owner command. Usage: !jump\nMake the bot reconnect to IRC", 101 | "quit": "Owner command. Usage: !quit\nMake the bot quit IRC", 102 | "owner": "Usage: !owner password\nAllow to become owner of the bot" 103 | } 104 | 105 | def __init__(self, my_pyborg, args): 106 | """ 107 | Args will be sys.argv (command prompt arguments) 108 | """ 109 | # PyBorg 110 | self.pyborg = my_pyborg 111 | # load settings 112 | 113 | self.settings = cfgfile.cfgset() 114 | self.settings.load("pyborg-irc.cfg", 115 | { "myname": ("The bot's nickname", "PyBorg"), 116 | "realname": ("Reported 'real name'", "Pyborg"), 117 | "localaddress": ("Local IP to bind to", ""), 118 | "ipv6": ("Whether to use IPv6", 0), 119 | "ssl": ("Whether to use SSL", 0), 120 | "owners": ("Owner(s) nickname", [ "OwnerNick" ]), 121 | "servers": ("IRC Server to connect to (server, port [,password])", [("irc.sucks.net", 6667)]), 122 | "chans": ("Channels to auto-join", ["#cutie578"]), 123 | "speaking": ("Allow the bot to talk on channels", 1), 124 | "stealth": ("Hide the fact we are a bot", 0), 125 | "ignorelist": ("Ignore these nicknames:", []), 126 | "reply2ignored": ("Reply to ignored people", 0), 127 | "reply_chance": ("Chance of reply (%) per message", 33), 128 | "quitmsg": ("IRC quit message", "Bye :-("), 129 | "password": ("password for control the bot (Edit manually !)", ""), 130 | "autosaveperiod": ("Save every X minutes. Leave at 0 for no saving.", 60), 131 | "nickserv": ("username and password for nickserv", ("", "")) 132 | }) 133 | 134 | # If autosaveperiod is set, trigger it. 135 | asp = self.settings.autosaveperiod 136 | if(asp > 0) : 137 | self.autosave_schedule(asp) 138 | 139 | # Create useful variables. 140 | self.owners = self.settings.owners[:] 141 | self.chans = self.settings.chans[:] 142 | self.inchans = [] 143 | self.wanted_myname = self.settings.myname 144 | self.attempting_regain = False 145 | self.feature_monitor = False 146 | 147 | # Parse command prompt parameters 148 | 149 | for x in xrange(1, len(args)): 150 | # Specify servers 151 | if args[x] == "-s": 152 | self.settings.servers = [] 153 | # Read list of servers 154 | for y in xrange(x+1, len(args)): 155 | if args[y][0] == "-": 156 | break 157 | server = args[y].split(":") 158 | # Default port if none specified 159 | if len(server) == 1: 160 | server.append("6667") 161 | self.settings.servers.append((server[0], int(server[1]))) 162 | # Channels 163 | if args[x] == "-c": 164 | self.settings.chans = [] 165 | # Read list of channels 166 | for y in xrange(x+1, len(args)): 167 | if args[y][0] == "-": 168 | break 169 | self.settings.chans.append("#"+args[y]) 170 | # Nickname 171 | if args[x] == "-n": 172 | try: 173 | self.settings.myname = args[x+1] 174 | except IndexError: 175 | pass 176 | 177 | def our_start(self): 178 | print "Connecting to server..." 179 | SingleServerIRCBot.__init__(self, self.settings.servers, 180 | self.settings.myname, self.settings.realname, 2, 181 | self.settings.localaddress, self.settings.ipv6, 182 | self.settings.ssl) 183 | 184 | self.connection.execute_delayed(20, self._chan_checker) 185 | self.connection.execute_delayed(20, self._nick_checker) 186 | self.start() 187 | 188 | def on_welcome(self, c, e): 189 | print self.chans 190 | if self.settings.nickserv and self.settings.nickserv[0] != '': 191 | if len(self.settings.nickserv) == 2 and self.settings.nickserv[1] != '': 192 | c.privmsg('NickServ', 'identify ' + self.settings.nickserv[0] + ' ' + self.settings.nickserv[1]) 193 | else: 194 | c.privmsg('NickServ', 'identify ' + self.settings.nickserv) 195 | for i in self.chans: 196 | c.join(i) 197 | 198 | def shutdown(self): 199 | try: 200 | self.die() # disconnect from server 201 | except AttributeError, e: 202 | # already disconnected probably (pingout or whatever) 203 | pass 204 | 205 | def get_version(self): 206 | if self.settings.stealth: 207 | # stealth mode. we shall be a windows luser today 208 | return "VERSION mIRC32 v5.6 K.Mardam-Bey" 209 | else: 210 | return self.pyborg.ver_string 211 | 212 | def on_kick(self, c, e): 213 | """ 214 | Process leaving 215 | """ 216 | # Parse Nickname!username@host.mask.net to Nickname 217 | kicked = e.arguments()[0] 218 | kicker = e.source().split("!")[0] 219 | target = e.target() #channel 220 | if len(e.arguments()) >= 2: 221 | reason = e.arguments()[1] 222 | else: 223 | reason = "" 224 | 225 | if kicked == self.settings.myname: 226 | print "[%s] <-- %s was kicked off %s by %s (%s)" % (get_time(), kicked, target, kicker, reason) 227 | self.inchans.remove(target.lower()) 228 | 229 | def on_part(self, c, e): 230 | """ 231 | Process leaving 232 | """ 233 | # Parse Nickname!username@host.mask.net to Nickname 234 | parter = e.source().split("!")[0] 235 | 236 | if parter == self.settings.myname: 237 | target = e.target() #channel 238 | self.inchans.remove(target.lower()) 239 | 240 | def on_join(self, c, e): 241 | """ 242 | Process Joining 243 | """ 244 | # Parse Nickname!username@host.mask.net to Nickname 245 | joiner = e.source().split("!")[0] 246 | 247 | if joiner == self.settings.myname: 248 | target = e.target() #channel 249 | self.inchans.append(target.lower()) 250 | 251 | def on_privmsg(self, c, e): 252 | self.on_msg(c, e) 253 | 254 | def on_featurelist(self, c, e): 255 | for feature in e.arguments(): 256 | if feature[:8] == "MONITOR=": 257 | print "MONITOR supported." 258 | self.feature_monitor = True 259 | c.send_raw("MONITOR + %s" % self.wanted_myname) 260 | break 261 | 262 | def _failed_new_nickname(self, c, e): 263 | if self.attempting_regain is False: 264 | self.settings.myname = c.get_nickname()[:8] + `random.randint(0, 9)` 265 | self.connection.nick(self.settings.myname) 266 | else: 267 | if self.feature_monitor: 268 | # A collision may have occurred, check again. 269 | c.send_raw("MONITOR s") 270 | self.settings.myname = c.get_nickname() 271 | self.attempting_regain = False 272 | 273 | def on_nicknameinuse(self, c, e): 274 | self._failed_new_nickname(c, e) 275 | 276 | def on_erroneusnickname(self, c, e): 277 | self._failed_new_nickname( c, e) 278 | 279 | # def on_unavailresource(self, c, e): 280 | # self._failed_new_nickname(c, e) 281 | 282 | def on_pubmsg(self, c, e): 283 | self.on_msg(c, e) 284 | 285 | def on_ctcp(self, c, e): 286 | ctcptype = e.arguments()[0] 287 | if ctcptype == "ACTION": 288 | self.on_msg(c, e) 289 | else: 290 | SingleServerIRCBot.on_ctcp(self, c, e) 291 | 292 | def _on_disconnect(self, c, e): 293 | # self.channels = IRCDict() 294 | print "deconnection" 295 | self.attempting_regain = False 296 | self.feature_monitor = False 297 | self.connection.execute_delayed(self.reconnection_interval, self._connected_checker) 298 | 299 | 300 | def on_msg(self, c, e): 301 | """ 302 | Process messages. 303 | """ 304 | # Parse Nickname!username@host.mask.net to Nickname 305 | source = e.source().split("!")[0] 306 | target = e.target() 307 | 308 | learn = 1 309 | 310 | # First message from owner 'locks' the owner host mask 311 | # se people can't change to the owner nick and do horrible 312 | # stuff like '!unlearn the' :-) 313 | if not e.source() in self.owner_mask and source in self.owners: 314 | self.owner_mask.append(e.source()) 315 | print "Locked owner as %s" % e.source() 316 | 317 | # Message text 318 | if len(e.arguments()) == 1: 319 | # Normal message 320 | body = e.arguments()[0] 321 | else: 322 | # A CTCP thing 323 | if e.arguments()[0] == "ACTION": 324 | body = source + " " + e.arguments()[1] 325 | else: 326 | # Ignore all the other CTCPs 327 | return 328 | # Ignore lines with color 329 | if body.find("\x03") != -1: return 330 | if body.find("\033") != -1: return 331 | 332 | #remove special irc fonts chars 333 | body = re.sub("[\x02\xa0]", "", body) 334 | 335 | # WHOOHOOO!! 336 | if target == self.settings.myname or source == self.settings.myname: 337 | print "[%s] <%s> > %s> %s" % (get_time(), source, target, body) 338 | 339 | # Ignore self. 340 | if source == self.settings.myname: return 341 | 342 | body_contains_me = body.lower().find(self.settings.myname.lower()) != -1 343 | 344 | #replace nicknames by "#nick" 345 | if e.eventtype() == "pubmsg": 346 | escaped_users = map(re.escape, self.channels[target].users()) 347 | # Match nicks on word boundaries to avoid rewriting words incorrectly as containing nicks. 348 | p = re.compile(r'\b(' + ('|'.join(escaped_users)) + r')\b') 349 | body = p.sub('#nick', body) 350 | print body 351 | 352 | # Ignore selected nicks 353 | if self.settings.ignorelist.count(source.lower()) > 0 \ 354 | and self.settings.reply2ignored == 1: 355 | print "Nolearn from %s" % source 356 | learn = 0 357 | elif self.settings.ignorelist.count(source.lower()) > 0: 358 | print "Ignoring %s" % source 359 | return 360 | 361 | # Stealth mode. disable commands for non owners 362 | if (not source in self.owners) and self.settings.stealth: 363 | while body[:1] == "!": 364 | body = body[1:] 365 | 366 | if body == "": 367 | return 368 | 369 | # Ignore quoted messages 370 | if body[0] == "<" or body[0:1] == "\"" or body[0:1] == " <": 371 | print "Ignoring quoted text" 372 | return 373 | 374 | # We want replies reply_chance%, if speaking is on 375 | replyrate = self.settings.speaking * self.settings.reply_chance 376 | 377 | # double reply chance if the text contains our nickname :-) 378 | if body_contains_me: 379 | replyrate = replyrate * 2 380 | 381 | # Always reply to private messages 382 | if e.eventtype() == "privmsg": 383 | replyrate = 100 384 | 385 | # Parse ModIRC commands 386 | if body[0] == "!": 387 | if self.irc_commands(body, source, target, c, e) == 1:return 388 | 389 | 390 | # Pass message onto pyborg 391 | if source in self.owners and e.source() in self.owner_mask: 392 | self.pyborg.process_msg(self, body, replyrate, learn, (body, source, target, c, e), owner=1) 393 | else: 394 | #start a new thread 395 | thread.start_new_thread(self.pyborg.process_msg, (self, body, replyrate, learn, (body, source, target, c, e))) 396 | 397 | def irc_commands(self, body, source, target, c, e): 398 | """ 399 | Special IRC commands. 400 | """ 401 | msg = "" 402 | 403 | command_list = body.split() 404 | command_list[0] = command_list[0].lower() 405 | 406 | ### User commands 407 | # Query replyrate 408 | if command_list[0] == "!replyrate" and len(command_list)==1: 409 | msg = "Reply rate is "+`self.settings.reply_chance`+"%." 410 | 411 | if command_list[0] == "!owner" and len(command_list) > 1 and source not in self.owners: 412 | if command_list[1] == self.settings.password: 413 | self.owners.append(source) 414 | self.output("You've been added to owners list", ("", source, target, c, e)) 415 | else: 416 | self.output("Try again", ("", source, target, c, e)) 417 | 418 | ### Owner commands 419 | if source in self.owners and e.source() in self.owner_mask: 420 | 421 | # Change nick 422 | if command_list[0] == "!nick": 423 | try: 424 | self.connection.nick(command_list[1]) 425 | self.settings.myname = command_list[1] 426 | self.wanted_myname = self.settings.myname 427 | except: 428 | pass 429 | # stealth mode 430 | elif command_list[0] == "!stealth": 431 | msg = "Stealth mode " 432 | if len(command_list) == 1: 433 | if self.settings.stealth == 0: 434 | msg = msg + "off" 435 | else: 436 | msg = msg + "on" 437 | else: 438 | toggle = command_list[1].lower() 439 | if toggle == "on": 440 | msg = msg + "on" 441 | self.settings.stealth = 1 442 | else: 443 | msg = msg + "off" 444 | self.settings.stealth = 0 445 | 446 | # filter mirc colours out? 447 | elif command_list[0] == "!nocolor" or command_list[0] == "!nocolour": 448 | msg = "obsolete command " 449 | 450 | # Allow/disallow replying to ignored nicks 451 | # (they will never be learnt from) 452 | elif command_list[0] == "!reply2ignored": 453 | msg = "Replying to ignored users " 454 | if len(command_list) == 1: 455 | if self.settings.reply2ignored == 0: 456 | msg = msg + "off" 457 | else: 458 | msg = msg + "on" 459 | else: 460 | toggle = command_list[1] 461 | if toggle == "on": 462 | msg = msg + "on" 463 | self.settings.reply2ignored = 1 464 | else: 465 | msg = msg + "off" 466 | self.settings.reply2ignored = 0 467 | # Stop talking 468 | elif command_list[0] == "!shutup": 469 | if self.settings.speaking == 1: 470 | msg = "I'll be quiet :-(" 471 | self.settings.speaking = 0 472 | else: 473 | msg = ":-x" 474 | # Wake up again 475 | elif command_list[0] == "!wakeup": 476 | if self.settings.speaking == 0: 477 | self.settings.speaking = 1 478 | msg = "Whoohoo!" 479 | else: 480 | msg = "But i'm already awake..." 481 | 482 | # Join a channel or list of channels 483 | elif command_list[0] == "!join": 484 | for x in xrange(1, len(command_list)): 485 | if not command_list[x] in self.chans: 486 | self.chans.append(command_list[x]) 487 | if not command_list[x].lower() in self.inchans: 488 | msg = "Attempting to join channel %s" % command_list[x] 489 | c.join(command_list[x]) 490 | 491 | # Part a channel or list of channels 492 | elif command_list[0] == "!part": 493 | for x in xrange(1, len(command_list)): 494 | if command_list[x] in self.chans: 495 | self.chans.remove(command_list[x]) 496 | if command_list[x].lower() in self.inchans: 497 | msg = "Leaving channel %s" % command_list[x] 498 | c.part(command_list[x]) 499 | 500 | # List channels currently on 501 | elif command_list[0] == "!chans": 502 | if len(self.channels.keys())==0: 503 | msg = "I'm currently on no channels" 504 | else: 505 | msg = "I'm currently on " 506 | channels = self.channels.keys() 507 | for x in xrange(0, len(channels)): 508 | msg = msg+channels[x]+" " 509 | # add someone to the ignore list 510 | elif command_list[0] == "!ignore": 511 | # if no arguments are given say who we are 512 | # ignoring 513 | if len(command_list) == 1: 514 | msg = "I'm ignoring " 515 | if len(self.settings.ignorelist) == 0: 516 | msg = msg + "nobody" 517 | else: 518 | for x in xrange(0, len(self.settings.ignorelist)): 519 | msg = msg + self.settings.ignorelist[x] + " " 520 | # Add everyone listed to the ignore list 521 | # eg !ignore tom dick harry 522 | else: 523 | for x in xrange(1, len(command_list)): 524 | self.settings.ignorelist.append(command_list[x].lower()) 525 | msg = "done" 526 | # remove someone from the ignore list 527 | elif command_list[0] == "!unignore": 528 | # Remove everyone listed from the ignore list 529 | # eg !unignore tom dick harry 530 | for x in xrange(1, len(command_list)): 531 | try: 532 | self.settings.ignorelist.remove(command_list[x].lower()) 533 | msg = "done" 534 | except: 535 | pass 536 | # set the quit message 537 | elif command_list[0] == "!quitmsg": 538 | if len(command_list) > 1: 539 | self.settings.quitmsg = body.split(" ", 1)[1] 540 | msg = "New quit message is \"%s\"" % self.settings.quitmsg 541 | else: 542 | msg = "Quit message is \"%s\"" % self.settings.quitmsg 543 | # make the pyborg quit 544 | elif command_list[0] == "!quit": 545 | sys.exit() 546 | elif command_list[0] == "!jump": 547 | print("Jumping servers...") 548 | self.jump_server() 549 | # Change reply rate 550 | elif command_list[0] == "!replyrate": 551 | try: 552 | self.settings.reply_chance = int(command_list[1]) 553 | msg = "Now replying to %d%% of messages." % int(command_list[1]) 554 | except: 555 | msg = "Reply rate is %d%%." % self.settings.reply_chance 556 | #make the bot talk 557 | elif command_list[0] == "!talk": 558 | if len(command_list) >= 2: 559 | phrase="" 560 | for x in xrange (2, len (command_list)): 561 | phrase = phrase + str(command_list[x]) + " " 562 | self.output(phrase, ("", command_list[1], "", c, e)) 563 | #make the bot /me 564 | elif command_list[0] == "!me": 565 | if len(command_list) >= 2: 566 | phrase="" 567 | for x in xrange (2, len (command_list)): 568 | phrase = phrase + str(command_list[x]) + " " 569 | self.output("\x01ACTION " + phrase + "\x01", ("", command_list[1], "", c, e)) 570 | # Save changes 571 | save_myname = self.settings.myname 572 | if self.wanted_myname is not None: 573 | self.settings.myname = self.wanted_myname 574 | self.pyborg.settings.save() 575 | self.settings.save() 576 | self.settings.myname = save_myname 577 | 578 | if msg == "": 579 | return 0 580 | else: 581 | self.output(msg, ("", source, target, c, e)) 582 | return 1 583 | 584 | 585 | def _chan_checker(self): 586 | if self.connection.is_connected(): 587 | for i in self.chans: 588 | if not i.split()[0].lower() in self.inchans: 589 | print "Attempting to rejoin %s" % i 590 | self.connection.join(i) 591 | self.connection.execute_delayed(20, self._chan_checker) 592 | 593 | def _nick_checker(self): 594 | if (self.connection.is_connected() and 595 | self.feature_monitor is False and 596 | self.connection.get_nickname() != self.wanted_myname): 597 | self.connection.ison([self.wanted_myname]) 598 | self.connection.execute_delayed(20, self._nick_checker) 599 | 600 | def _try_regain(self, nick): 601 | print "Attempting to regain nickname %s" % nick 602 | self.attempting_regain = True 603 | self.settings.myname = nick 604 | self.connection.nick(self.settings.myname) 605 | 606 | def on_ison(self, c, e): 607 | nick_found = False 608 | for nick in e.arguments()[0].split(): 609 | if nick.lower() == self.wanted_myname.lower(): 610 | nick_found = True 611 | break 612 | if not nick_found: 613 | self._try_regain(self.wanted_myname) 614 | 615 | def on_monoffline(self, c, e): 616 | for nick in e.arguments()[0].split(','): 617 | if nick.lower() == self.wanted_myname.lower(): 618 | self._try_regain(self.wanted_myname) 619 | break 620 | 621 | def output(self, message, args): 622 | """ 623 | Output a line of text. args = (body, source, target, c, e) 624 | """ 625 | if not self.connection.is_connected(): 626 | print "Can't send reply : not connected to server" 627 | return 628 | 629 | # Unwrap arguments 630 | body, source, target, c, e = args 631 | 632 | # replace by the good nickname 633 | message = message.replace("#nick :", "#nick:") 634 | message = message.replace("#nick", source) 635 | 636 | # Decide. should we do a ctcp action? 637 | if message.find(self.settings.myname.lower()+" ") == 0: 638 | action = 1 639 | message = message[len(self.settings.myname)+1:] 640 | else: 641 | action = 0 642 | 643 | # Joins replies and public messages 644 | if e.eventtype() == "join" or e.eventtype() == "quit" or e.eventtype() == "part" or e.eventtype() == "pubmsg": 645 | if action == 0: 646 | print "[%s] <%s> > %s> %s" % (get_time(), self.settings.myname, target, message) 647 | c.privmsg(target, message) 648 | else: 649 | print "[%s] <%s> > %s> /me %s" % (get_time(), self.settings.myname, target, message) 650 | c.action(target, message) 651 | # Private messages 652 | elif e.eventtype() == "privmsg": 653 | # normal private msg 654 | if action == 0: 655 | print "[%s] <%s> > %s> %s" % (get_time(), self.settings.myname, source, message) 656 | c.privmsg(source, message) 657 | # send copy to owner 658 | if not source in self.owners: 659 | c.privmsg(','.join(self.owners), "(From "+source+") "+body) 660 | c.privmsg(','.join(self.owners), "(To "+source+") "+message) 661 | # ctcp action priv msg 662 | else: 663 | print "[%s] <%s> > %s> /me %s" % (get_time(), self.settings.myname, target, message) 664 | c.action(source, message) 665 | # send copy to owner 666 | if not source in self.owners: 667 | map ((lambda x: c.action(x, "(From "+source+") "+body)), self.owners) 668 | map ((lambda x: c.action(x, "(To "+source+") "+message)), self.owners) 669 | 670 | ## 671 | # This function schedules autosave_execute to happen every asp minutes 672 | # @param asp the autosave period, configured on pyborg-irc.cfg, in minutes. 673 | def autosave_schedule(self, asp) : 674 | timer = Timer(asp * 60, self.autosave_execute, ()) 675 | self.should_autosave = True 676 | timer.setDaemon(True) 677 | timer.start() 678 | 679 | ## 680 | # This function gets called every autosaveperiod minutes, and executes the autosaving. 681 | # @param asp autosaveperiod, see above. 682 | def autosave_execute(self) : 683 | if self.should_autosave: 684 | self.pyborg.save_all() 685 | self.autosave_schedule(self.settings.autosaveperiod) 686 | 687 | def autosave_stop(self): 688 | self.should_autosave = False 689 | 690 | if __name__ == "__main__": 691 | 692 | if "--help" in sys.argv: 693 | print "Pyborg irc bot. Usage:" 694 | print " pyborg-irc.py [options]" 695 | print " -s server:port" 696 | print " -c channel" 697 | print " -n nickname" 698 | print "Defaults stored in pyborg-irc.cfg" 699 | print 700 | sys.exit(0) 701 | # start the pyborg 702 | signal.signal(signal.SIGTERM, sigterm_handler) 703 | signal.signal(signal.SIGHUP, sigterm_handler) 704 | my_pyborg = pyborg.pyborg() 705 | bot = ModIRC(my_pyborg, sys.argv) 706 | try: 707 | bot.our_start() 708 | except KeyboardInterrupt, e: 709 | pass 710 | except SystemExit, e: 711 | pass 712 | except OSError, e: 713 | if e.errno == errno.ENXIO or e.errno == errno.EPIPE or e.errno == errno.EINTR: 714 | pass 715 | else: 716 | raise 717 | except IOError, e: 718 | if e.errno == errno.ENXIO or e.errno == errno.EPIPE or e.errno == errno.EINTR: 719 | pass 720 | else: 721 | raise 722 | except: 723 | traceback.print_exc() 724 | c = raw_input("Ooops! It looks like Pyborg has crashed. Would you like to save its dictionary? (y/n) ") 725 | if c.lower()[:1] == 'n': 726 | sys.exit(0) 727 | try: 728 | signal.signal(signal.SIGTERM, signal.SIG_IGN) 729 | signal.signal(signal.SIGHUP, signal.SIG_IGN) 730 | signal.signal(signal.SIGPIPE, signal.SIG_IGN) 731 | signal.signal(signal.SIGINT, signal.SIG_IGN) 732 | bot.autosave_stop() 733 | bot.disconnect(bot.settings.quitmsg) 734 | if my_pyborg.saving: 735 | while my_pyborg.saving: 736 | print "Waiting for save in other thread..." 737 | time.sleep(1) 738 | else: 739 | my_pyborg.save_all(exiting=True) 740 | del my_pyborg 741 | except OSError, e: 742 | if e.errno == errno.ENXIO or e.errno == errno.EPIPE or e.errno == errno.EINTR: 743 | pass 744 | else: 745 | raise 746 | except IOError, e: 747 | if e.errno == errno.ENXIO or e.errno == errno.EPIPE or e.errno == errno.EINTR: 748 | pass 749 | else: 750 | raise 751 | -------------------------------------------------------------------------------- /lib/pyborg/pyborg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: set sw=4 sts=4 ts=8 et: 3 | """ 4 | # PyBorg: The python AI bot. 5 | # 6 | # Copyright (c) 2000, 2006 Tom Morton, Sebastien Dailly 7 | # 8 | # 9 | # This bot was inspired by the PerlBorg, by Eric Bock. 10 | # 11 | # This program is free software; you can redistribute it and/or 12 | # modify it under the terms of the GNU General Public License 13 | # as published by the Free Software Foundation; either version 2 14 | # of the License, or (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 24 | # 25 | # Tom Morton 26 | # Seb Dailly 27 | """ 28 | 29 | import random 30 | import sys 31 | import os 32 | import marshal # buffered marshal is bloody fast. wish i'd found this before :) 33 | import struct 34 | import time 35 | import zipfile 36 | import re 37 | from atomicfile import AtomicFile 38 | import ctypes 39 | 40 | 41 | def filter_message(message, bot): 42 | """ 43 | Filter a message body so it is suitable for learning from and 44 | replying to. This involves removing confusing characters, 45 | padding ? and ! with ". " so they also terminate lines 46 | and converting to lower case. 47 | """ 48 | 49 | 50 | # to lowercase 51 | message = message.lower() 52 | 53 | # remove garbage 54 | message = message.replace("\"", "") # remove "s 55 | message = message.replace("'", "") # remove 's 56 | message = message.replace("\n", " ") # remove newlines 57 | message = message.replace("\r", " ") # remove carriage returns 58 | 59 | # remove matching brackets (unmatched ones are likely smileys :-) *cough* 60 | # should except out when not found. 61 | index = 0 62 | try: 63 | while 1: 64 | index = message.index("(", index) 65 | # Remove matching) bracket 66 | i = message.index(")", index + 1) 67 | message = message[0:i] + message[i + 1:] 68 | # And remove the ( 69 | message = message[0:index] + message[index + 1:] 70 | except ValueError: 71 | pass 72 | 73 | # No sense in keeping URLS 74 | message = re.sub(r"https?://[^ ]* ", "", message) 75 | 76 | message = message.replace("; ", ", ") 77 | for split_char in ['?', '!', '.', ',']: 78 | message = message.replace(split_char, " %c " % split_char) 79 | # message = message.replace("'", " ' ") 80 | # message = re.sub(r"\b:", " : ", message) 81 | message = message.replace("#nick:", "#nick :") 82 | 83 | # Find ! and ? and append full stops. 84 | # message = message.replace(". ", ".. ") 85 | # message = message.replace("? ", "?. ") 86 | # message = message.replace("! ", "!. ") 87 | 88 | #And correct the '...' 89 | # message = message.replace(".. .. .. ", ".... ") 90 | 91 | words = message.split() 92 | if bot.settings.process_with == "pyborg": 93 | for x in xrange(0, len(words)): 94 | #is there aliases ? 95 | for z in bot.settings.aliases.keys(): 96 | for alias in bot.settings.aliases[z]: 97 | pattern = "^%s$" % alias 98 | if re.search(pattern, words[x]): 99 | words[x] = z 100 | 101 | message = " ".join(words) 102 | return message 103 | 104 | 105 | class pyborg: 106 | import cfgfile 107 | 108 | ver_string = "I am a version 1.1.2 PyBorg" 109 | saves_version = "1.1.0" 110 | 111 | saving = False 112 | 113 | # Main command list 114 | commandlist = "Pyborg commands:\n!checkdict, !contexts, !help, !known, !learning, !rebuilddict, \ 115 | !replace, !unlearn, !purge, !version, !words, !limit, !alias, !save, !censor, !uncensor, !owner" 116 | commanddict = { 117 | "help": "Owner command. Usage: !help [command]\nPrints information about using a command, or a list of commands if no command is given", 118 | "version": "Usage: !version\nDisplay what version of Pyborg we are running", 119 | "words": "Usage: !words\nDisplay how many words are known", 120 | "known": "Usage: !known word1 [word2 [...]]\nDisplays if one or more words are known, and how many contexts are known", 121 | "contexts": "Owner command. Usage: !contexts \nPrint contexts containing ", 122 | "unlearn": "Owner command. Usage: !unlearn \nRemove all occurances of a word or expression from the dictionary. For example '!unlearn of of' would remove all contexts containing double 'of's", 123 | "purge": "Owner command. Usage: !purge [number]\nRemove up to words that appears in less than 2 contexts. Specify 0 to see how many are eligible to remove.", 124 | "replace": "Owner command. Usage: !replace \nReplace all occurances of word in the dictionary with ", 125 | "learning": "Owner command. Usage: !learning [on|off]\nToggle bot learning. Without arguments shows the current setting", 126 | "checkdict": "Owner command. Usage: !checkdict\nChecks the dictionary for broken links. Shouldn't happen, but worth trying if you get KeyError crashes", 127 | "rebuilddict": "Owner command. Usage: !rebuilddict\nRebuilds dictionary links from the lines of known text. Takes a while. You probably don't need to do it unless your dictionary is very screwed", 128 | "censor": "Owner command. Usage: !censor [word1 [...]]\nPrevent the bot using one or more words. Without arguments lists the currently censored words", 129 | "uncensor": "Owner command. Usage: !uncensor word1 [word2 [...]]\nRemove censorship on one or more words", 130 | "limit": "Owner command. Usage: !limit [number]\nSet the number of words that pyBorg can learn", 131 | "alias": "Owner command. Usage: !alias : Show the differents aliases\n!alias : show the words attached to this alias\n!alias : link the word to the alias", 132 | "owner": "Usage : !owner password\nAdd the user in the owner list" 133 | } 134 | 135 | def __init__(self): 136 | """ 137 | Open the dictionary. Resize as required. 138 | """ 139 | # Attempt to load settings 140 | self.settings = self.cfgfile.cfgset() 141 | self.settings.load("pyborg.cfg", 142 | { "num_contexts": ("Total word contexts", 0), 143 | "num_words": ("Total unique words known", 0), 144 | "max_words": ("max limits in the number of words known", 6000), 145 | "learning": ("Allow the bot to learn", 1), 146 | "ignore_list":("Words that can be ignored for the answer", ['!.', '?.', "'", ',', ';']), 147 | "censored": ("Don't learn the sentence if one of those words is found", []), 148 | "num_aliases":("Total of aliases known", 0), 149 | "aliases": ("A list of similars words", {}), 150 | "process_with":("Wich way for generate the reply (pyborg|megahal)", "pyborg"), 151 | "no_save" :("If True, Pyborg don't saves the dictionary and configuration on disk", "False") 152 | }) 153 | 154 | self.answers = self.cfgfile.cfgset() 155 | self.answers.load("answers.txt", 156 | { "sentences": ("A list of prepared answers", {}) 157 | }) 158 | self.unfilterd = {} 159 | 160 | # Read the dictionary 161 | if self.settings.process_with == "pyborg": 162 | print "Reading dictionary..." 163 | try: 164 | with zipfile.ZipFile('archive.zip', 'r') as zfile: 165 | with zfile.open('version', 'r') as content: 166 | if content.read() != self.saves_version: 167 | print "Error loading dictionary\nPlease convert it before launching pyborg" 168 | sys.exit(1) 169 | 170 | with zfile.open('words.dat', 'r') as content: 171 | self.words = marshal.loads(content.read()) 172 | with zfile.open('lines.dat', 'r') as content: 173 | self.lines = marshal.loads(content.read()) 174 | except (EOFError, IOError): 175 | # Create mew database 176 | self.words = {} 177 | self.lines = {} 178 | print "Error reading saves. New database created." 179 | 180 | # Is a resizing required? 181 | if len(self.words) != self.settings.num_words: 182 | print "Updating dictionary information..." 183 | self.settings.num_words = len(self.words) 184 | num_contexts = 0 185 | # Get number of contexts 186 | for x in self.lines.keys(): 187 | num_contexts += len(self.lines[x][0].split()) 188 | self.settings.num_contexts = num_contexts 189 | # Save new values 190 | self.settings.save() 191 | 192 | # Is an aliases update required ? 193 | compteur = 0 194 | for x in self.settings.aliases.keys(): 195 | compteur += len(self.settings.aliases[x]) 196 | if compteur != self.settings.num_aliases: 197 | print "check dictionary for new aliases" 198 | self.settings.num_aliases = compteur 199 | 200 | for x in self.words.keys(): 201 | #is there aliases ? 202 | if x[0] != '~': 203 | for z in self.settings.aliases.keys(): 204 | for alias in self.settings.aliases[z]: 205 | pattern = "^%s$" % alias 206 | if self.re.search(pattern, x): 207 | print "replace %s with %s" % (x, z) 208 | self.replace(x, z) 209 | 210 | for x in self.words.keys(): 211 | if not (x in self.settings.aliases.keys()) and x[0] == '~': 212 | print "unlearn %s" % x 213 | self.settings.num_aliases -= 1 214 | self.unlearn(x) 215 | print "unlearned aliases %s" % x 216 | 217 | 218 | #unlearn words in the unlearn.txt file. 219 | try: 220 | f = open("unlearn.txt", "r") 221 | while 1: 222 | word = f.readline().strip('\n') 223 | if word == "": 224 | break 225 | if self.words.has_key(word): 226 | self.unlearn(word) 227 | f.close() 228 | except (EOFError, IOError): 229 | # No words to unlearn 230 | pass 231 | 232 | self.settings.save() 233 | 234 | @staticmethod 235 | def read_file(file_name): 236 | """ Return the content of a File 237 | """ 238 | with open(file_name, 'rb') as f: 239 | return f.read() 240 | 241 | ## 242 | # Saves all dictionaries and words and contexts, everything. 243 | # @return returns true if successfully saved, or false if it failed. 244 | def save_all(self, exiting=False): 245 | if self.saving: 246 | print "Cannot save because currently saving." 247 | return False 248 | else: 249 | self.saving = True 250 | try: 251 | if self.settings.process_with == "pyborg" and self.settings.no_save != "True": 252 | print "Writing dictionary..." 253 | 254 | with zipfile.ZipFile('archive.zip.tmp', 'w', 255 | zipfile.ZIP_DEFLATED, compresslevel=3) as z: 256 | s = marshal.dumps(self.words) 257 | z.writestr('words.dat', s) 258 | del s 259 | s = marshal.dumps(self.lines) 260 | if exiting: 261 | del self.lines 262 | z.writestr('lines.dat', s) 263 | del s 264 | #save the version 265 | z.writestr('version', self.saves_version) 266 | os.rename('archive.zip.tmp', 'archive.zip') 267 | 268 | for filename, data in [ 269 | ('words.txt', self.words), 270 | ('sentences.txt', self.unfilterd), 271 | ]: 272 | with AtomicFile(filename, 'w') as f: 273 | # write each words known 274 | wordlist = [] 275 | #Sort the list befor to export 276 | for key in data.keys(): 277 | wordlist.append([key, len(data[key])]) 278 | wordlist.sort(lambda x, y: cmp(x[1], y[1])) 279 | #map((lambda x: f.write(str(x[0]) + "\n\r")), wordlist) 280 | [ f.write(str(x[0]) + "\n\r") for x in wordlist] 281 | 282 | # Save settings 283 | self.settings.save() 284 | 285 | print "Dictionary saved." 286 | return True 287 | finally: 288 | if os.path.exists('archive.zip.tmp'): 289 | os.unlink('archive.zip.tmp') 290 | self.saving = False 291 | 292 | def process_msg(self, io_module, body, replyrate, learn, args, owner = 0): 293 | """ 294 | Process message 'body' and pass back to IO module with args. 295 | If owner==1 allow owner commands. 296 | """ 297 | 298 | try: 299 | if self.settings.process_with == "megahal": 300 | import mh_python 301 | except: 302 | self.settings.process_with = "pyborg" 303 | self.settings.save() 304 | print "Could not find megahal python library\nProgram ending" 305 | sys.exit(1) 306 | 307 | # add trailing space so sentences are broken up correctly 308 | body = body + " " 309 | 310 | # Parse commands 311 | if body[0] == "!": 312 | self.do_commands(io_module, body, args, owner) 313 | return 314 | 315 | # Filter out garbage and do some formatting 316 | body = filter_message(body, self) 317 | 318 | # Learn from input 319 | if learn == 1: 320 | if self.settings.process_with == "pyborg": 321 | self.learn(body) 322 | elif self.settings.process_with == "megahal" and self.settings.learning == 1: 323 | mh_python.learn(body) 324 | 325 | 326 | # Make a reply if desired 327 | if random.randint(0, 99) < replyrate: 328 | 329 | message = "" 330 | 331 | #Look if we can find a prepared answer 332 | for sentence in self.answers.sentences.keys(): 333 | pattern = "^%s$" % sentence 334 | if re.search(pattern, body): 335 | message = self.answers.sentences[sentence][random.randint(0, len(self.answers.sentences[sentence]) - 1)] 336 | break 337 | else: 338 | if body in self.unfilterd: 339 | self.unfilterd[body] = self.unfilterd[body] + 1 340 | else: 341 | self.unfilterd[body] = 0 342 | 343 | if message == "": 344 | if self.settings.process_with == "pyborg": 345 | message = self.reply(body) 346 | elif self.settings.process_with == "megahal": 347 | message = mh_python.doreply(body) 348 | 349 | # single word reply: always output 350 | if len(message.split()) == 1: 351 | io_module.output(message, args) 352 | return 353 | # empty. do not output 354 | if message == "": 355 | return 356 | # else output 357 | if owner == 0: 358 | time.sleep(.2 * len(message)) 359 | io_module.output(message, args) 360 | 361 | def do_commands(self, io_module, body, args, owner): 362 | """ 363 | Respond to user comands. 364 | """ 365 | msg = "" 366 | 367 | command_list = body.split() 368 | command_list[0] = command_list[0].lower() 369 | 370 | # Guest commands. 371 | 372 | # Version string 373 | if command_list[0] == "!version": 374 | msg = self.ver_string 375 | 376 | # How many words do we know? 377 | elif command_list[0] == "!words" and self.settings.process_with == "pyborg": 378 | num_w = self.settings.num_words 379 | num_c = self.settings.num_contexts 380 | num_l = len(self.lines) 381 | if num_w != 0: 382 | num_cpw = num_c / float(num_w) # contexts per word 383 | else: 384 | num_cpw = 0.0 385 | msg = "I know %d words (%d contexts, %.2f per word), %d lines." % (num_w, num_c, num_cpw, num_l) 386 | 387 | # Do i know this word 388 | elif command_list[0] == "!known" and self.settings.process_with == "pyborg": 389 | words = (x.lower() for x in command_list[1:]) 390 | msg = "Number of contexts: " 391 | for word in words: 392 | if self.words.has_key(word): 393 | contexts = len(self.words[word]) 394 | msg += word + "/%i " % contexts 395 | else: 396 | msg += word + "/unknown " 397 | msg = msg.replace("#nick", "$nick") 398 | 399 | # Owner commands 400 | if owner == 1: 401 | # Save dictionary 402 | if command_list[0] == "!save": 403 | if self.save_all(): 404 | msg = "Dictionary saved" 405 | else: 406 | msg = "Already saving" 407 | 408 | # Command list 409 | elif command_list[0] == "!help": 410 | if len(command_list) > 1: 411 | # Help for a specific command 412 | cmd = command_list[1].lower() 413 | dic = None 414 | if cmd in self.commanddict.keys(): 415 | dic = self.commanddict 416 | elif cmd in io_module.commanddict.keys(): 417 | dic = io_module.commanddict 418 | if dic: 419 | for i in dic[cmd].split("\n"): 420 | io_module.output(i, args) 421 | else: 422 | msg = "No help on command '%s'" % cmd 423 | else: 424 | for i in self.commandlist.split("\n"): 425 | io_module.output(i, args) 426 | for i in io_module.commandlist.split("\n"): 427 | io_module.output(i, args) 428 | 429 | # Change the max_words setting 430 | elif command_list[0] == "!limit" and self.settings.process_with == "pyborg": 431 | msg = "The max limit is " 432 | if len(command_list) == 1: 433 | msg += str(self.settings.max_words) 434 | else: 435 | limit = int(command_list[1].lower()) 436 | self.settings.max_words = limit 437 | msg += "now " + command_list[1] 438 | 439 | 440 | # Check for broken links in the dictionary 441 | elif command_list[0] == "!checkdict" and self.settings.process_with == "pyborg": 442 | t = time.time() 443 | num_broken = 0 444 | num_bad = 0 445 | for w in self.words.keys(): 446 | wlist = self.words[w] 447 | 448 | for i in xrange(len(wlist) - 1, -1, -1): 449 | if len(wlist[i]) == 10: 450 | line_idx, word_num = struct.unpack("lH", wlist[i]) 451 | else: 452 | line_idx, word_num = struct.unpack("iH", wlist[i]) 453 | 454 | # Nasty critical error we should fix 455 | if not self.lines.has_key(line_idx): 456 | print "Removing broken link '%s' -> %d" % (w, line_idx) 457 | num_broken = num_broken + 1 458 | del wlist[i] 459 | else: 460 | # Check pointed to word is correct 461 | split_line = self.lines[line_idx][0].split() 462 | if split_line[word_num] != w: 463 | print "Line '%s' word %d is not '%s' as expected." % \ 464 | (self.lines[line_idx][0], 465 | word_num, w) 466 | num_bad = num_bad + 1 467 | del wlist[i] 468 | if len(wlist) == 0: 469 | del self.words[w] 470 | self.settings.num_words = self.settings.num_words - 1 471 | print "\"%s\" vaped totally" % w 472 | 473 | msg = "Checked dictionary in %0.2fs. Fixed links: %d broken, %d bad." % \ 474 | (time.time() - t, 475 | num_broken, 476 | num_bad) 477 | 478 | # Rebuild the dictionary by discarding the word links and 479 | # re-parsing each line 480 | elif command_list[0] == "!rebuilddict" and self.settings.process_with == "pyborg": 481 | if self.settings.learning == 1: 482 | t = time.time() 483 | 484 | old_lines = self.lines 485 | old_num_words = self.settings.num_words 486 | old_num_contexts = self.settings.num_contexts 487 | 488 | self.words = {} 489 | self.lines = {} 490 | self.settings.num_words = 0 491 | self.settings.num_contexts = 0 492 | 493 | for k in old_lines.keys(): 494 | self.learn(old_lines[k][0], old_lines[k][1]) 495 | 496 | msg = "Rebuilt dictionary in %0.2fs. Words %d (%+d), contexts %d (%+d)" % \ 497 | (time.time() - t, 498 | old_num_words, 499 | self.settings.num_words - old_num_words, 500 | old_num_contexts, 501 | self.settings.num_contexts - old_num_contexts) 502 | 503 | #Remove rares words 504 | elif command_list[0] == "!purge" and self.settings.process_with == "pyborg": 505 | t = time.time() 506 | 507 | liste = [] 508 | compteur = 0 509 | 510 | if len(command_list) == 2: 511 | # limite d occurences a effacer 512 | c_max = command_list[1].lower() 513 | else: 514 | c_max = 0 515 | 516 | c_max = int(c_max) 517 | 518 | for w in self.words.keys(): 519 | 520 | digit = 0 521 | char = 0 522 | for c in w: 523 | if c.isalpha(): 524 | char += 1 525 | if c.isdigit(): 526 | digit += 1 527 | 528 | 529 | #Compte les mots inferieurs a cette limite 530 | c = len(self.words[w]) 531 | if c < 2 or (digit and char): 532 | liste.append(w) 533 | compteur += 1 534 | if compteur == c_max: 535 | break 536 | 537 | if c_max < 1: 538 | #io_module.output(str(compteur)+" words to remove", args) 539 | io_module.output("%s words to remove" % compteur, args) 540 | return 541 | 542 | #supprime les mots 543 | [self.unlearn(w) for w in liste[0:]] 544 | 545 | 546 | msg = "Purge dictionary in %0.2fs. %d words removed" % \ 547 | (time.time() - t, 548 | compteur) 549 | 550 | # Change a typo in the dictionary 551 | elif command_list[0] == "!replace" and self.settings.process_with == "pyborg": 552 | if len(command_list) < 3: 553 | return 554 | old = command_list[1].lower() 555 | new = command_list[2].lower() 556 | msg = self.replace(old, new) 557 | 558 | # Print contexts [flooding...:-] 559 | elif command_list[0] == "!contexts" and self.settings.process_with == "pyborg": 560 | # This is a large lump of data and should 561 | # probably be printed, not module.output XXX 562 | 563 | # build context we are looking for 564 | context = " ".join(command_list[1:]) 565 | context = context.lower() 566 | if context == "": 567 | return 568 | io_module.output("Contexts containing \"" + context + "\":", args) 569 | # Build context list 570 | # Pad it 571 | context = " " + context + " " 572 | c = [] 573 | # Search through contexts 574 | for x in self.lines.keys(): 575 | # get context 576 | ctxt = self.lines[x][0] 577 | # add leading whitespace for easy sloppy search code 578 | ctxt = " " + ctxt + " " 579 | if ctxt.find(context) != -1: 580 | # Avoid duplicates (2 of a word 581 | # in a single context) 582 | if len(c) == 0: 583 | c.append(self.lines[x][0]) 584 | elif c[len(c) - 1] != self.lines[x][0]: 585 | c.append(self.lines[x][0]) 586 | x = 0 587 | while x < 5: 588 | if x < len(c): 589 | io_module.output(c[x], args) 590 | x += 1 591 | if len(c) == 5: 592 | return 593 | if len(c) > 10: 594 | io_module.output("...(" + `len(c) - 10` + " skipped)...", args) 595 | x = len(c) - 5 596 | if x < 5: 597 | x = 5 598 | while x < len(c): 599 | io_module.output(c[x], args) 600 | x += 1 601 | 602 | # Remove a word from the vocabulary [use with care] 603 | elif command_list[0] == "!unlearn" and self.settings.process_with == "pyborg": 604 | # build context we are looking for 605 | context = " ".join(command_list[1:]) 606 | context = context.lower() 607 | if context == "": 608 | return 609 | print "Looking for: " + context 610 | # Unlearn contexts containing 'context' 611 | t = time.time() 612 | self.unlearn(context) 613 | # we don't actually check if anything was 614 | # done.. 615 | msg = "Unlearn done in %0.2fs" % (time.time() - t) 616 | 617 | # Query/toggle bot learning 618 | elif command_list[0] == "!learning": 619 | msg = "Learning mode " 620 | if len(command_list) == 1: 621 | if self.settings.learning == 0: 622 | msg += "off" 623 | else: 624 | msg += "on" 625 | else: 626 | toggle = command_list[1].lower() 627 | if toggle == "on": 628 | msg += "on" 629 | self.settings.learning = 1 630 | else: 631 | msg += "off" 632 | self.settings.learning = 0 633 | 634 | # add a word to the 'censored' list 635 | elif command_list[0] == "!censor" and self.settings.process_with == "pyborg": 636 | # no arguments. list censored words 637 | if len(command_list) == 1: 638 | if len(self.settings.censored) == 0: 639 | msg = "No words censored" 640 | else: 641 | msg = "I will not use the word(s) %s" % ", ".join(self.settings.censored) 642 | # add every word listed to censored list 643 | else: 644 | for x in xrange(1, len(command_list)): 645 | if command_list[x] in self.settings.censored: 646 | msg += "%s is already censored" % command_list[x] 647 | else: 648 | self.settings.censored.append(command_list[x].lower()) 649 | self.unlearn(command_list[x]) 650 | msg += "done" 651 | msg += "\n" 652 | 653 | # remove a word from the censored list 654 | elif command_list[0] == "!uncensor" and self.settings.process_with == "pyborg": 655 | # Remove everyone listed from the ignore list 656 | # eg !unignore tom dick harry 657 | for x in xrange(1, len(command_list)): 658 | try: 659 | self.settings.censored.remove(command_list[x].lower()) 660 | msg = "done" 661 | except ValueError: 662 | pass 663 | 664 | elif command_list[0] == "!alias" and self.settings.process_with == "pyborg": 665 | # no arguments. list aliases words 666 | if len(command_list) == 1: 667 | if len(self.settings.aliases) == 0: 668 | msg = "No aliases" 669 | else: 670 | msg = "I will alias the word(s) %s" \ 671 | % ", ".join(self.settings.aliases.keys()) 672 | # add every word listed to alias list 673 | elif len(command_list) == 2: 674 | if command_list[1][0] != '~': command_list[1] = '~' + command_list[1] 675 | if command_list[1] in self.settings.aliases.keys(): 676 | msg = "Thoses words : %s are aliases to %s" \ 677 | % (" ".join(self.settings.aliases[command_list[1]]), command_list[1]) 678 | else: 679 | msg = "The alias %s is not known" % command_list[1][1:] 680 | elif len(command_list) > 2: 681 | #create the aliases 682 | msg = "The words : " 683 | if command_list[1][0] != '~': command_list[1] = '~' + command_list[1] 684 | if not(command_list[1] in self.settings.aliases.keys()): 685 | self.settings.aliases[command_list[1]] = [command_list[1][1:]] 686 | self.replace(command_list[1][1:], command_list[1]) 687 | msg += command_list[1][1:] + " " 688 | for x in xrange(2, len(command_list)): 689 | msg += "%s " % command_list[x] 690 | self.settings.aliases[command_list[1]].append(command_list[x]) 691 | #replace each words by his alias 692 | self.replace(command_list[x], command_list[1]) 693 | msg += "have been aliases to %s" % command_list[1] 694 | 695 | # Quit 696 | elif command_list[0] == "!quit": 697 | # Close the dictionary 698 | self.save_all() 699 | sys.exit() 700 | 701 | # Save changes 702 | self.settings.save() 703 | 704 | if msg != "": 705 | io_module.output(msg, args) 706 | 707 | def replace(self, old, new): 708 | """ 709 | Replace all occuraces of 'old' in the dictionary with 710 | 'new'. Nice for fixing learnt typos. 711 | """ 712 | try: 713 | pointers = self.words[old] 714 | except KeyError: 715 | return old + " not known." 716 | changed = 0 717 | 718 | for x in pointers: 719 | # pointers consist of (line, word) to self.lines 720 | if len(x) == 10: 721 | l, w = struct.unpack("lH", x) 722 | else: 723 | l, w = struct.unpack("iH", x) 724 | line = self.lines[l][0].split() 725 | number = self.lines[l][1] 726 | if line[w] != old: 727 | # fucked dictionary 728 | print "Broken link: %s %s" % (x, self.lines[l][0]) 729 | continue 730 | else: 731 | line[w] = new 732 | self.lines[l][0] = " ".join(line) 733 | self.lines[l][1] += number 734 | changed += 1 735 | 736 | if self.words.has_key(new): 737 | self.settings.num_words -= 1 738 | self.words[new].extend(self.words[old]) 739 | else: 740 | self.words[new] = self.words[old] 741 | del self.words[old] 742 | return "%d instances of %s replaced with %s" % (changed, old, new) 743 | 744 | def unlearn(self, context): 745 | """ 746 | Unlearn all contexts containing 'context'. If 'context' 747 | is a single word then all contexts containing that word 748 | will be removed, just like the old !unlearn 749 | """ 750 | # Pad thing to look for 751 | # We pad so we don't match 'shit' when searching for 'hit', etc. 752 | context = " " + context + " " 753 | # Search through contexts 754 | # count deleted items 755 | dellist = [] 756 | # words that will have broken context due to this 757 | wordlist = [] 758 | for x in self.lines.keys(): 759 | # get context. pad 760 | c = " " + self.lines[x][0] + " " 761 | if c.find(context) != -1: 762 | # Split line up 763 | wlist = self.lines[x][0].split() 764 | # add touched words to list 765 | for w in wlist: 766 | if not w in wordlist: 767 | wordlist.append(w) 768 | dellist.append(x) 769 | del self.lines[x] 770 | words = self.words 771 | # update links 772 | for x in wordlist: 773 | word_contexts = words[x] 774 | # Check all the word's links (backwards so we can delete) 775 | for y in xrange(len(word_contexts) - 1, -1, -1): 776 | # Check for any of the deleted contexts 777 | if len(word_contexts[y]) == 10: 778 | unpacked = struct.unpack( "lH", word_contexts[y] )[0] 779 | else: 780 | unpacked = struct.unpack( "iH", word_contexts[y] )[0] 781 | if unpacked in dellist: 782 | del word_contexts[y] 783 | self.settings.num_contexts = self.settings.num_contexts - 1 784 | if len(words[x]) == 0: 785 | del words[x] 786 | self.settings.num_words = self.settings.num_words - 1 787 | print "\"%s\" vaped totally" % x 788 | 789 | def reply(self, body): 790 | """ 791 | Reply to a line of text. 792 | """ 793 | # split sentences into list of words 794 | _words = body.split(" ") 795 | words = [] 796 | for i in _words: 797 | words += i.split() 798 | del _words 799 | 800 | if len(words) == 0: 801 | return "" 802 | 803 | #remove words on the ignore list 804 | #words = filter((lambda x: x not in self.settings.ignore_list and not x.isdigit()), words) 805 | words = (x for x in words if x not in self.settings.ignore_list and not x.isdigit()) 806 | 807 | # Find rarest word (excluding those unknown) 808 | index = [] 809 | known = -1 810 | #The word has to be seen in already 3 contexts differents for being choosen 811 | known_min = 3 812 | for x in words: 813 | if self.words.has_key(x): 814 | k = len(self.words[x]) 815 | else: 816 | continue 817 | if (known == -1 or k < known) and k > known_min: 818 | index = [x] 819 | known = k 820 | continue 821 | elif k == known: 822 | index.append(x) 823 | continue 824 | # Index now contains list of rarest known words in sentence 825 | if len(index) == 0: 826 | return "" 827 | word = index[random.randint(0, len(index) - 1)] 828 | 829 | # Build sentence backwards from "chosen" word 830 | sentence = [word] 831 | done = 0 832 | while done == 0: 833 | #create a dictionary wich will contain all the words we can found before the "chosen" word 834 | pre_words = {"" : 0} 835 | #this is for prevent the case when we have an ignore_listed word 836 | word = str(sentence[0].split(" ")[0]) 837 | for x in xrange(0, len(self.words[word]) - 1): 838 | if len(self.words[word][x]) == 10: 839 | l, w = struct.unpack("lH", self.words[word][x]) 840 | else: 841 | l, w = struct.unpack("iH", self.words[word][x]) 842 | context = self.lines[l][0] 843 | num_context = self.lines[l][1] 844 | cwords = context.split() 845 | #if the word is not the first of the context, look the previous one 846 | if cwords[w] != word: 847 | print context 848 | if w: 849 | #look if we can found a pair with the choosen word, and the previous one 850 | if len(sentence) > 1 and len(cwords) > w + 1: 851 | if sentence[1] != cwords[w + 1]: 852 | continue 853 | 854 | #if the word is in ignore_list, look the previous word 855 | look_for = cwords[w - 1] 856 | if look_for in self.settings.ignore_list and w > 1: 857 | look_for = cwords[w - 2] + " " + look_for 858 | 859 | #saves how many times we can found each word 860 | if not(pre_words.has_key(look_for)): 861 | pre_words[look_for] = num_context 862 | else : 863 | pre_words[look_for] += num_context 864 | 865 | else: 866 | pre_words[""] += num_context 867 | 868 | #Sort the words 869 | liste = pre_words.items() 870 | liste.sort(lambda x, y: cmp(y[1], x[1])) 871 | 872 | numbers = [liste[0][1]] 873 | for x in xrange(1, len(liste)): 874 | numbers.append(liste[x][1] + numbers[x - 1]) 875 | 876 | #take one them from the list (randomly) 877 | mot = random.randint(0, numbers[len(numbers) - 1]) 878 | for x in xrange(0, len(numbers)): 879 | if mot <= numbers[x]: 880 | mot = liste[x][0] 881 | break 882 | 883 | #if the word is already choosen, pick the next one 884 | while mot in sentence: 885 | x += 1 886 | if x >= len(liste) - 1: 887 | mot = '' 888 | break 889 | mot = liste[x][0] 890 | 891 | mot = mot.split(" ") 892 | mot.reverse() 893 | if mot == ['']: 894 | done = 1 895 | else: 896 | #map((lambda x: sentence.insert(0, x)), mot) 897 | [sentence.insert(0, x) for x in mot] 898 | 899 | pre_words = sentence 900 | sentence = sentence[-2:] 901 | 902 | # Now build sentence forwards from "chosen" word 903 | 904 | #We've got 905 | #cwords: ... cwords[w-1] cwords[w] cwords[w+1] cwords[w+2] 906 | #sentence: ... sentence[-2] sentence[-1] look_for look_for ? 907 | 908 | #we are looking, for a cwords[w] known, and maybe a cwords[w-1] known, what will be the cwords[w+1] to choose. 909 | #cwords[w+2] is need when cwords[w+1] is in ignored list 910 | 911 | 912 | done = 0 913 | while done == 0: 914 | #create a dictionary wich will contain all the words we can found before the "chosen" word 915 | post_words = {"" : 0} 916 | word = str(sentence[-1].split(" ")[-1]) 917 | for x in self.words[word]: 918 | if len(x) == 10: 919 | l, w = struct.unpack("lH", x) 920 | else: 921 | l, w = struct.unpack("iH", x) 922 | context = self.lines[l][0] 923 | num_context = self.lines[l][1] 924 | cwords = context.split() 925 | #look if we can found a pair with the choosen word, and the next one 926 | if len(sentence) > 1: 927 | if sentence[len(sentence) - 2] != cwords[w - 1]: 928 | continue 929 | 930 | if w < len(cwords) - 1: 931 | #if the word is in ignore_list, look the next word 932 | look_for = cwords[w + 1] 933 | if look_for in self.settings.ignore_list and w < len(cwords) - 2: 934 | look_for = look_for + " " + cwords[w + 2] 935 | 936 | if not(post_words.has_key(look_for)): 937 | post_words[look_for] = num_context 938 | else : 939 | post_words[look_for] += num_context 940 | else: 941 | post_words[""] += num_context 942 | #Sort the words 943 | liste = post_words.items() 944 | liste.sort(lambda x, y: cmp(y[1], x[1])) 945 | numbers = [liste[0][1]] 946 | 947 | for x in xrange(1, len(liste)): 948 | numbers.append(liste[x][1] + numbers[x - 1]) 949 | 950 | #take one them from the list (randomly) 951 | mot = random.randint(0, numbers[len(numbers) - 1]) 952 | for x in xrange(0, len(numbers)): 953 | if mot <= numbers[x]: 954 | mot = liste[x][0] 955 | break 956 | 957 | x = -1 958 | while mot in sentence: 959 | x += 1 960 | if x >= len(liste) - 1: 961 | mot = '' 962 | break 963 | mot = liste[x][0] 964 | 965 | 966 | mot = mot.split(" ") 967 | if mot == ['']: 968 | done = 1 969 | else: 970 | [ sentence.append(x) for x in mot] 971 | #map((lambda x: sentence.append(x)), mot) 972 | 973 | sentence = pre_words[:-2] + sentence 974 | 975 | #Replace aliases 976 | for x in xrange(0, len(sentence)): 977 | if sentence[x][0] == "~": 978 | sentence[x] = sentence[x][1:] 979 | 980 | #Insert space between each words 981 | #map((lambda x: sentence.insert(1 + x * 2, " ")), xrange(0, len(sentence) - 1)) 982 | [sentence.insert(1 + x * 2, " ") for x in xrange(0, len(sentence) - 1)] 983 | 984 | #correct the ' & , spaces problem 985 | #code is not very good and can be improve but does his job... 986 | for x in xrange(0, len(sentence)): 987 | if sentence[x] == "'": 988 | sentence[x - 1] = "" 989 | if x + 1 < len(sentence): 990 | sentence[x + 1] = "" 991 | for split_char in ['?', '!', ',']: 992 | if sentence[x] == split_char: 993 | sentence[x - 1] = "" 994 | 995 | #return as string.. 996 | return "".join(sentence) 997 | 998 | def learn(self, body, num_context = 1): 999 | """ 1000 | Lines should be cleaned (filter_message()) before passing 1001 | to this. 1002 | """ 1003 | 1004 | def learn_line(self, body, num_context): 1005 | """ 1006 | Learn from a sentence. 1007 | """ 1008 | 1009 | words = body.split() 1010 | # Ignore sentences of < 1 words XXX was < 3 1011 | if len(words) < 1: 1012 | return 1013 | 1014 | voyelles = "aàâeéèêiîïoöôuüûyaAeEiIoOuUyY" 1015 | for x in xrange(0, len(words)): 1016 | 1017 | nb_voy = 0 1018 | digit = 0 1019 | char = 0 1020 | for c in words[x]: 1021 | if c in voyelles: 1022 | nb_voy += 1 1023 | if c.isalpha(): 1024 | char += 1 1025 | if c.isdigit(): 1026 | digit += 1 1027 | 1028 | for censored in self.settings.censored: 1029 | pattern = "^%s$" % censored 1030 | if re.search(pattern, words[x]): 1031 | print "Censored word %s" % words[x] 1032 | return 1033 | 1034 | if len(words[x]) > 13 \ 1035 | or (((nb_voy * 100) / len(words[x]) < 21) and len(words[x]) > 5) \ 1036 | or (char and digit) \ 1037 | or (self.words.has_key(words[x]) == 0 and self.settings.learning == 0): 1038 | #if one word as more than 13 characters, don't learn 1039 | # (in french, this represent 12% of the words) 1040 | #and don't learn words where there are less than 20% of voyels 1041 | #don't learn the sentence if one word is censored 1042 | #don't learn too if there are digits and char in the word 1043 | #same if learning is off 1044 | return 1045 | elif ("-" in words[x] or "_" in words[x]) : 1046 | words[x] = "#nick" 1047 | 1048 | 1049 | num_w = self.settings.num_words 1050 | if num_w != 0: 1051 | num_cpw = self.settings.num_contexts / float(num_w) # contexts per word 1052 | else: 1053 | num_cpw = 0 1054 | 1055 | cleanbody = " ".join(words) 1056 | 1057 | # Hash collisions we don't care about. 2^32 is big :-) 1058 | hashval = ctypes.c_int32(hash(cleanbody)).value 1059 | 1060 | # Check context isn't already known 1061 | if not self.lines.has_key(hashval): 1062 | if not(num_cpw > 100 and self.settings.learning == 0): 1063 | 1064 | self.lines[hashval] = [cleanbody, num_context] 1065 | # Add link for each word 1066 | for x in xrange(0, len(words)): 1067 | if self.words.has_key(words[x]): 1068 | # Add entry. (line number, word number) 1069 | self.words[words[x]].append(struct.pack("lH", hashval, x)) 1070 | else: 1071 | self.words[words[x]] = [ struct.pack("lH", hashval, x) ] 1072 | self.settings.num_words += 1 1073 | self.settings.num_contexts += 1 1074 | else : 1075 | self.lines[hashval][1] += num_context 1076 | 1077 | #is max_words reached, don't learn more 1078 | if self.settings.num_words >= self.settings.max_words: 1079 | self.settings.learning = 0 1080 | 1081 | # Split body text into sentences and parse them 1082 | # one by one. 1083 | body += " " 1084 | #map ((lambda x : learn_line(self, x, num_context)), body.split(". ")) 1085 | [learn_line(self, x, num_context) for x in body.split(". ")] 1086 | --------------------------------------------------------------------------------