Labo

Sign in or create your account | Project List | Help

Labo Commit Details

Date:2008-11-26 16:57:21 (1 year 9 months ago)
Author:naps
Commit:360825a0f8edbd5049779dec0ed834125d2c20e7
Message:Import of wiioa (old svn-revisions lost)

Files: wiioa/bot.py (1 diff)
wiioa/pyquake3.py (1 diff)
wiioa/wiioa.py (1 diff)

Change Details

wiioa/bot.py
1#!/usr/bin/python
2import socket, os, string, sys, re, signal
3
4""" Creates an IRC bot that connects to an IRC server and parses received text.
5You can delve as deep into the connection with the IRC server as you choose.
6Subclass IRCBot and reimplement a high-level callback for specifc events such as text spoken in
7a channel optionally reimplement IRCBot.act which passes raw received data from
8IRC straight to you - it's your decision. See the online documentation at http://cerulean.pyresoft.com/docs/ircbot for a very simple sample and member reference.
9Changelog:
10 - Sat Jan 8 2005: Added IRCBot.userEntered(user, channel) callback - called when a new user enters a channel, as well as fixing bugs with IRCBot.messageFromSender. """
11
12""" The debug level. Set to False to not display debug output. """
13debug = True
14
15def dbg(s):
16    """ Write output for debugging purpose. """
17    if debug:
18        print s
19
20class IRCBot:
21    """ The L{IRCBot} class provides a full IRC bot framework for connection to an IRC server. Once
22    connected, the bot parses received text and calls functions upon significant events, i.e a message has been received in a channel, or a private message has been sent by another user.
23
24    One can subclass this class and reimplement a higher-level callback (L{text received in a channel<messageFromChannel>}) or can optionally reimplement a L{lower-level function<act>} which receives raw data from the IRC server.
25    A very simple example is as follows. It connects to an IRC server and joins two channels. If someone in either of those channels says a string beginning with 'hello' it responds with a greeting:
26
27        >>> class HelloBot(IRCBot):
28        ... def messageFromChannel(self, channel, user, message):
29        ... if message.startswith("hello"):
30        ... self.sendMessageToChannel(channel, "Hello to you too " + user + "!")
31
32        >>> bot = HelloBot("HelloBot", "irc.freenode.net", ["#channel1", "#channel2"])
33        >>> bot.execute() ; bot.close()
34
35
36    """
37    def __init__(self, nick, server, channels):
38        """ Constructor to initiate and store all the information needed for a connection to the IRC server.
39        @type nick: string
40        @param nick: The nickname of the bot.
41        @type server: string
42        @param server: The IRC server to connect to.
43        @type channels: list
44        @param channels: The IRC channels to join upon connection. The channels are joined in IRCBot.firstTime. """
45        self.firstconnection = True
46        self.nick = nick
47        self.server = server
48        self.channels = channels
49        self.port = 6666
50        self.name = "A hyper-intelligent shade of blue"
51        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
52
53    def execute(self):
54        """ Runs the bot's main loop, reading data from the IRC server and acting accordingly. """
55        self.startConnection()
56        olddata = ""
57        while 1:
58            # First read the data.
59            try:
60                data = string.strip(self.recv(1024))
61            except socket.error, msg:
62                pass
63            # If we received \0 then exit, we're done.
64            if not data:
65                break
66            dbg("bot <- " + data)
67
68            # If we're ping'd then pong back and return.
69            if data.startswith('PING '):
70                self.send("PONG :" + data[6:])
71            else:
72                # Main parsing
73                self.baseAct(data)
74
75                # If we just connected to IRC call the firsttime function.
76                if self.firstconnection:
77                    self.firstTime(data)
78                    self.firstconnection = False
79
80    def close(self):
81        """ Closes the connection with the IRC server. """
82        self.sock.close()
83
84    def firstTime(self, data):
85        """ Callback called when the bot has just initiated the connection with the IRC server.
86        Reimplement to your liking. The default behavior is to join the channels you specified in the constructor.
87        @type data: string
88        @param data: The data received from the IRC server upon first connection. """
89        for channel in self.channels:
90            self.send("JOIN " + channel)
91
92    def sendMessageToChannel(self, channel, message):
93        """ Send a message to a channel.
94        @type channel: string
95        @param channel: The channel to send the message to.
96        @type message: string
97        @param message: The message to send to the channel. """
98        self.send("PRIVMSG " + channel + " :" + message)
99
100    #- Callbacks you may implement instead of delving into the raw data passed to IRCBot.act
101    def messageFromChannel(self, channel, user, message):
102        """ Callback called when a message is received from a channel the bot is residing in.
103        @type channel: string
104        @param channel: The channel from which a message has been received.
105        @type user: string
106        @param user: The user who sent the message in the channel.
107        @type message: string
108        @param message: The message received. """
109        dbg("Received message from '" + str(user) + "' in channel '" + channel + "':\n" + message)
110
111    def sendMessageToChannel(self, channel, message):
112        """ Sends a message to a channel.
113        @type channel: string
114        @param channel: The channel to send the message to.
115        @type message: string
116        @param message: The message to be sent to the channel. """
117        self.send("PRIVMSG " + channel + " :" + message)
118
119    def messageFromUser(self, user, message, msgtype=None):
120        """ Callback called when a message is received from a user.
121        @type user: string
122        @param user: The user who sent the message to the bot.
123        @type message: string
124        @param message: The message received.
125        @type msgtype: integer
126        @param msgtype: The type of message that was sent (PRIVMSG, NOTICE, etc). Not implemented yet."""
127        dbg("Received message from '" + user + "':\n" + message)
128
129    def sendMessageToUser(self, user, message):
130        """ Sends a message to a user.
131        @type user: string
132        @param user: The user to send the message to.
133        @type message: string
134        @param message: The message to be sent to the channel. """
135        self.send("PRIVMSG " + user + " :" + message)
136
137    def userEntered(self, user, channel):
138        """ Callback called when a new user enters a channel you are in. """
139        dbg(user + " entered channel " + channel)
140    def act(self, data):
141        """ Callback which is passed raw data from the IRC server received from the socket.
142        @type data: string
143        @param data: The data received from the IRC server. """
144        pass
145
146    def reply(self, data, message):
147        """ Sends a message to either a channel or an individual, based on the text passed.
148        Raw IRC data passed is parsed and checked whether it is sent by a channel or by an individual. A message is then sent accordingly. This function only makes much sense in some of the lower level
149        callbacks where one cannot be certain who sent the message without regular expressions and such.
150        @type data: string
151        @param data: The unmodified string passed to the function by the server.
152        @type message: string
153        @param message: The message to send to the channel/user """
154        # call getChannel passing True as the username param.
155        self.sendMessageToChannel(self.getChannel(data), message, True)
156
157    def getSenderName(self, data):
158        """ Get the name of the person who sent a message, given the raw IRC message.
159        @type data: string
160        @param data: The raw IRC data from which to extract the nickname of the sender.
161        @rtype: string or None
162        @returns: The sender or None upon failure."""
163        try:
164            regSenderName = re.compile(":[\w\s]*")
165            return re.sub("^:", "", regSenderName.findall(data)[0])
166        except IndexError:
167            return None
168
169    def getChannel(self, data, username=True):
170        """ Gets the channel a message was sent from.
171        The function looks for the channel that a message came from once passed the raw IRC string it is in. If it does not succeed in finding the channel and the username parameter is set to True it will try and see if the message came from a user and not a channel, returning the nickname of the user if it then finds it.
172        If neither channel nor nickname are found then it returns None.
173        @type data: string
174        @param data: The raw IRC data from which to extract the channel.
175        @type username: boolean
176        @param username: True if the function should search for a username upon failure extracting the channel, False otherwise.
177        @rtype: string or None
178        @returns: The channel or user that sent a message or None upon failure. """
179        # Confusing and spaghetti-ish. @todo make self.reply handle the checking of user/channel.
180        if not data.count("#") and username:
181            return self.getSenderName(data)
182        else:
183            try:
184                channel = data[data.index("#"):]
185                return channel[:channel.index(" ")]
186            except ValueError:
187                return None
188
189    def recv(self, amount):
190        """ Receives text from the IRC server.
191        @type amount: integer
192        @param amount: The buffer size of the text being read.
193        @returns: The data read from the IRC server.
194        @rtype: string """
195        return self.sock.recv(amount)
196
197    def send(self, text):
198        """ Send a raw IRC command to the server.
199        @type text: string
200        @param text: The command to send to send to the IRC server. """
201        self.sock.send(text + "\n\r")
202        dbg("bot -> " + text)
203
204    def startConnection(self):
205        """ Connects to the IRC server and identifies the bot on it."""
206        try:
207            self.sock.connect((self.server, self.port))
208        except:
209            sys.stderr.write("Error connecting to " + self.server + " on port " + `self.port`)
210        usercommand = "USER " + self.nick + " cerulean.pyresoft.com cerulean.pyresoft.com :" + self.name
211        nickcommand = "NICK " + self.nick + " cerulean.pyresoft.com"
212        self.send(usercommand)
213        self.send(nickcommand)
214
215    def baseAct(self, data):
216        """ Parses the received data and responds accordingly. It parses the data and calls the necessary callbacks.
217        @type data: string
218        @param data: The data from the server to be parsed. """
219        # Call the callbacks.
220        channel = self.getChannel(data, False)
221        sender = self.getSenderName(data)
222        # messageFromChannel
223        if channel:
224            # Messages from a channel are sent by someone.. who is that someone:
225            if not sender:
226                sender = None
227            self.messageFromChannel(channel, sender, data[data.find(" :") + 2:])
228        # userEntered
229        elif sender and data[data.find(" ") + 1:].startswith("JOIN :"):
230            self.userEntered(sender, data[data.find("JOIN :") + 6:])
231        elif sender:
232            # Make sure this is a message (NOTICE or PRIVMSG).
233            ok = False
234            if data[data.find(" ") + 1:].startswith("NOTICE %s :" % self.nick):
235                ok = True
236            elif data[data.find(" ") + 1:].startswith("PRIVMSG %s :" % self.nick):
237                ok = True
238            # messageFromUser
239            if ok:
240                self.messageFromUser(sender, data[data.find(" :") + 2:])
241        # Pass the data to self.act
242        self.act(data)
243
244def main():
245    """ Called when this module is ran directly. """
246    class HelloBot(IRCBot):
247        def messageFromChannel(self, channel, user, message):
248            if message.startswith("hello"):
249                self.sendMessageToChannel(channel, "Hello to you too " + user + "!")
250
251    bot = HelloBot("HelloBot", "irc.freenode.net", ["#hellobot"])
252    bot.execute() ; bot.close()
253
254if __name__ == "__main__":
255    main()
wiioa/pyquake3.py
1"""
2Python Quake 3 Library
3http://misc.slowchop.com/misc/wiki/pyquake3
4Copyright (C) 2006-2007 Gerald Kaszuba
5
6This program is free software; you can redistribute it and/or
7modify it under the terms of the GNU General Public License
8as published by the Free Software Foundation; either version 2
9of the License, or (at your option) any later version.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19"""
20
21import socket
22import re
23
24class Player:
25    def __init__(self, name, frags, ping, address=None, bot=-1):
26        self.name = name
27        self.frags = frags
28        self.ping = ping
29        self.address = address
30        self.bot = bot
31    def __str__(self):
32        return self.name
33    def __repr__(self):
34        return str(self)
35
36class PyQuake3:
37    packet_prefix = '\xff' * 4
38    player_reo = re.compile(r'^(\d+) (\d+) "(.*)"')
39    def __init__(self, server, rcon_password=''):
40        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
41        self.set_server(server)
42        self.set_rcon_password(rcon_password)
43    def set_server(self, server):
44        try:
45            self.address, self.port = server.split(':')
46        except:
47            raise Exception('Server address must be in the format of \
48                    "address:port"')
49        self.port = int(self.port)
50        self.s.connect((self.address, self.port))
51    def get_address(self):
52        return '%s:%s' % (self.address, self.port)
53    def set_rcon_password(self, rcon_password):
54        self.rcon_password = rcon_password
55    def send_packet(self, data):
56        self.s.send('%s%s\n' % (self.packet_prefix, data))
57    def recv(self, timeout=1):
58        self.s.settimeout(timeout)
59        try:
60            return self.s.recv(4096)
61        except socket.error, e:
62            raise Exception('Error receiving the packet: %s' % \
63                    e[1])
64    def command(self, cmd, timeout=1, retries=3):
65        while retries:
66            self.send_packet(cmd)
67            try:
68                data = self.recv(timeout)
69            except:
70                data = None
71            if data:
72                return self.parse_packet(data)
73            retries -= 1
74        raise Exception('Server response timed out')
75    def rcon(self, cmd):
76        r = self.command('rcon "%s" %s' % (self.rcon_password, cmd))
77        if r[1] == 'No rconpassword set on the server.\n' or r[1] == \
78                'Bad rconpassword.\n':
79            raise Exception(r[1][:-1])
80        return r
81    def parse_packet(self, data):
82        if data.find(self.packet_prefix) != 0:
83            raise Exception('Malformed packet')
84        first_line_length = data.find('\n')
85        if first_line_length == -1:
86            raise Exception('Malformed packet')
87        response_type = data[len(self.packet_prefix):first_line_length]
88        response_data = data[first_line_length+1:]
89        return response_type, response_data
90    def parse_status(self, data):
91        split = data[1:].split('\\')
92        values = dict(zip(split[::2], split[1::2]))
93        # if there are \n's in one of the values, it's the list of players
94        for var, val in values.items():
95            pos = val.find('\n')
96            if pos == -1:
97                continue
98            split = val.split('\n', 1)
99            values[var] = split[0]
100            self.parse_players(split[1])
101        return values
102    def parse_players(self, data):
103        self.players = []
104        for player in data.split('\n'):
105            if not player:
106                continue
107            match = self.player_reo.match(player)
108            if not match:
109                print 'couldnt match', player
110                continue
111            frags, ping, name = match.groups()
112            self.players.append(Player(name, frags, ping))
113    def update(self):
114        cmd, data = self.command('getstatus')
115        self.vars = self.parse_status(data)
116    def rcon_update(self):
117        cmd, data = self.rcon('status')
118        lines = data.split('\n')
119        players = lines[3:]
120        self.players = []
121        for p in players:
122            while p.find(' ') != -1:
123                p = p.replace(' ', ' ')
124            while p.find(' ') == 0:
125                p = p[1:]
126            if p == '':
127                continue
128            p = p.split(' ')
129            self.players.append(Player(p[3][:-2], p[0], p[1], p[5], p[6]))
130
131if __name__ == '__main__':
132    q = PyQuake3('localhost:27960','hello')
133    q.update()
134    print 'The name of %s is %s, running map %s with %s player(s).' % \
135            (q.get_address(), q.vars['sv_hostname'], \
136            q.vars['mapname'], len(q.players))
137    for player in q.players:
138        print '%s with %s frags and a %sms ping' % (player.name, \
139                player.frags, player.ping)
140    q.rcon_update()
141    for player in q.players:
142        print '%s has an address of %s' % (player.name, player.address)
143
wiioa/wiioa.py
1# wiioa.py
2#
3# Copyright 2007 Antoine 'NaPs' Millet <antoine@inaps.org>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18# MA 02110-1301, USA.
19
20from pyquake3 import PyQuake3
21from bot import IRCBot
22
23class WiiOA(IRCBot):
24
25    oa = PyQuake3('saiks.org:27960', '')
26
27    def messageFromChannel(self, channel, user, message):
28        if message.startswith("oa ?"):
29            try:
30                self.oa.update()
31                players = self.oa.players
32                playerslist = []
33                for player in players:
34                    playerslist.append('%s (%s)' % (player.name, player.frags))
35                self.sendMessageToChannel(channel, "%s : La map en cours est %s (t: %s/%s)" % (user, self.oa.vars['mapname'], self.oa.vars['g_gametype'], self.oa.vars['fraglimit']))
36                if len(playerslist) == 0:
37                    self.sendMessageToChannel(channel, '%s : et personne ne joue sur le serveur :(.' % user)
38                elif len(playerslist) == 1:
39                    self.sendMessageToChannel(channel, '%s : et %s est tout seul sur le serveur :|.' % (user, playerslist[0]))
40                else:
41                    self.sendMessageToChannel(channel, "%s : et %s sont en train de jouer sur le serveur :)." % (user, ', '.join(playerslist)))
42            except:
43                self.sendMessageToChannel(channel, 'Saiks is dead, webs sucks.')
44        elif message.startswith('!say '):
45            self.oa.rcon('say (from IRC) <%s> %s' % (user, message.lstrip('!say ')))
46
47if __name__ == '__main__':
48    bot = WiiOA("WiiOA", "irc.freenode.net", ["#escalope"])
49    bot.execute() ; bot.close()

Archive Download the corresponding diff file

Branches:
master