示例#1
0
文件: core.py 项目: shoosen/ibid
class Address(Processor):

    priority = 1600
    processed = True
    addressed = False
    event_types = ('message', 'action', 'notice', 'state')

    acknowledgements = ListOption(
        'acknowledgements', 'Responses for positive acknowledgements',
        (u'Okay', u'Sure', u'Done', u'Righto', u'Alrighty', u'Yessir'))
    refusals = ListOption(
        'refusals', 'Responses for negative acknowledgements',
        (u'No', u"I won't", u"Shan't", u"I'm sorry, but I can't do that"))

    @handler
    def address(self, event):
        for response in event.responses:
            if isinstance(response['reply'], bool):
                if response:
                    response['reply'] = choice(self.acknowledgements)
                else:
                    response['reply'] = choice(self.refusals)
            if (response.get('address', False)
                    and not response.get('action', False)
                    and not response.get('notice', False) and event.public):
                response['reply'] = ('%s: %s' %
                                     (event.sender['nick'], response['reply']))
示例#2
0
class SourceFactory(client.DeferredClientFactory,
                    protocol.ReconnectingClientFactory,
                    IbidSourceFactory):
    auth = ('implicit',)
    supports = ('multiline',)

    jid_str = Option('jid', 'Jabber ID')
    server = Option('server', 'Server hostname (defaults to SRV lookup, '
                              'falling back to JID domain)')
    port = IntOption('port', 'Server port number (defaults to SRV lookup, '
                             'falling back to 5222/5223')
    ssl = BoolOption('ssl', 'Use SSL instead of automatic TLS')
    password = Option('password', 'Jabber password')
    nick = Option('nick', 'Nick for chatrooms', ibid.config['botname'])
    rooms = ListOption('rooms', 'Chatrooms to autojoin', [])
    accept_domains = ListOption('accept_domains',
            'Only accept messages from these domains', [])
    max_public_message_length = IntOption('max_public_message_length',
            'Maximum length of public messages', 512)

    def __init__(self, name):
        IbidSourceFactory.__init__(self, name)
        self.log = logging.getLogger('source.%s' % name)
        client.DeferredClientFactory.__init__(self, JID(self.jid_str),
                                              self.password)
        bot = JabberBot()
        self.addHandler(bot)
        bot.setHandlerParent(self)

    def setServiceParent(self, service):
        c = IbidXMPPClientConnector(reactor, self.authenticator.jid.host, self,
                                    self.server, self.port, self.ssl)
        c.connect()

    def connect(self):
        return self.setServiceParent(None)

    def disconnect(self):
        self.stopTrying()
        self.stopFactory()
        self.proto.xmlstream.transport.loseConnection()
        return True

    def join(self, room):
        return self.proto.join(room)

    def leave(self, room):
        return self.proto.leave(room)

    def url(self):
        return u'xmpp://%s' % (self.jid_str,)

    def logging_name(self, identity):
        return identity.split('/')[0]

    def truncation_point(self, response, event=None):
        if response.get('target', None) in self.proto.rooms:
            return self.max_public_message_length
        return None
示例#3
0
文件: factoid.py 项目: rsnyman/ibid
class Get(Processor, RPC):
    usage = u'<factoid> [( #<number> | /<pattern>/[r] )]'
    features = ('factoid', )

    priority = 200

    interrogatives = ListOption('interrogatives', 'Question words to strip',
                                default_interrogatives)
    verbs = ListOption('verbs', 'Verbs that split name from value',
                       default_verbs)

    def __init__(self, name):
        super(Get, self).__init__(name)
        RPC.__init__(self)

    def setup(self):
        self.get.im_func.pattern = re.compile(
            r'^(?:(?:%s)\s+(?:(?:%s)\s+)?)?(.+?)(?:\s+#(\d+))?(?:\s+/(.+?)/(r?))?$'
            % ('|'.join(self.interrogatives), '|'.join(self.verbs)), re.I)

    @handler
    def get(self, event, name, number, pattern, is_regex):
        response = self.remote_get(name, number, pattern, is_regex, event)
        if response:
            event.addresponse(response)

    def remote_get(self,
                   name,
                   number=None,
                   pattern=None,
                   is_regex=None,
                   event={}):
        factoid = get_factoid(event.session, name, number, pattern, is_regex)

        if factoid:
            (factoid, fname, fvalue) = factoid
            reply = fvalue.value
            oname = fname.name
            pattern = re.escape(fname.name).replace(r'\$arg', '(.*)')
            args = re.match(pattern, name, re.I | re.U).groups()

            for i, capture in enumerate(args):
                reply = reply.replace('$%s' % (i + 1), capture)
                oname = oname.replace('$arg', capture, 1)

            reply = _interpolate(reply, event)

            (reply, count) = action_re.subn('', reply)
            if count:
                return {'action': True, 'reply': reply}

            (reply, count) = reply_re.subn('', reply)
            if count:
                return {'address': False, 'reply': reply}

            reply = u'%s %s' % (oname, reply)
            return reply
示例#4
0
文件: core.py 项目: shoosen/ibid
class Addressed(Processor):

    priority = -1500
    addressed = False

    names = ListOption('names', 'Names to respond to',
                       [ibid.config['botname']])
    verbs = ListOption(
        'verbs', u'Verbs to ignore',
        ('is', 'has', 'was', 'might', 'may', 'would', 'will', "isn't",
         "hasn't", "wasn't", "wouldn't", "won't", 'can', "can't", 'did',
         "didn't", 'said', 'says', 'should', "shouldn't", 'does', "doesn't"))

    def setup(self):
        names = '|'.join(re.escape(x) for x in self.names)
        verbs = '|'.join(re.escape(x) for x in self.verbs)
        self.patterns = [
            re.compile(
                r'^\s*(?P<nick>%s)' % names +
                r'(?:\s*[:;.?>!,-]+\s+|\s+|\s*[,:]\s*)(?P<body>.*)',
                re.I | re.DOTALL),
            # "hello there, bot"-style addressing. But we want to be sure that
            # there wasn't normal addressing too:
            re.compile(
                r'^(?:\S+:.*|(?P<body>.*),\s*(?P<nick>%s))[\s?!.]*$' % names,
                re.I | re.DOTALL)
        ]
        self.verb_pattern = re.compile(r'^(?:%s)\s+(?:%s)\s+' % (names, verbs),
                                       re.I | re.DOTALL)

    @handler
    def handle_addressed(self, event):
        if 'addressed' not in event:
            event.addressed = False

        if self.verb_pattern.match(event.message['stripped']):
            return

        for pattern in self.patterns:
            matches = pattern.search(event.message['stripped'])
            if matches and matches.group('nick'):
                new_message = matches.group('body')
                event.addressed = matches.group('nick')
                event.message['clean'] = new_message
                event.message['deaddressed'] = \
                        pattern.search(event.message['raw']).group('body')
示例#5
0
class Lengthen(Processor):
    usage = u"""<url>
    expand <url>"""
    feature = ('tinyurl', )

    services = ListOption('services',
                          'List of URL prefixes of URL shortening services', (
                              'http://is.gd/',
                              'http://tinyurl.com/',
                              'http://ff.im/',
                              'http://shorl.com/',
                              'http://icanhaz.com/',
                              'http://url.omnia.za.net/',
                              'http://snipurl.com/',
                              'http://tr.im/',
                              'http://snipr.com/',
                              'http://bit.ly/',
                              'http://cli.gs/',
                              'http://zi.ma/',
                              'http://twurl.nl/',
                              'http://xrl.us/',
                              'http://lnk.in/',
                              'http://url.ie/',
                              'http://ne1.net/',
                              'http://turo.us/',
                              'http://301url.com/',
                              'http://u.nu/',
                              'http://twi.la/',
                              'http://ow.ly/',
                              'http://su.pr/',
                              'http://tiny.cc/',
                              'http://ur1.ca/',
                          ))

    def setup(self):
        self.lengthen.im_func.pattern = re.compile(
            r'^(?:((?:%s)\S+)|(?:lengthen\s+|expand\s+)(http://\S+))$' %
            '|'.join([re.escape(service) for service in self.services]),
            re.I | re.DOTALL)

    @handler
    def lengthen(self, event, url1, url2):
        url = url1 or url2
        opener = build_opener(NullRedirect())
        try:
            f = opener.open(url)
            f.close()
        except HTTPError, e:
            if e.code in (301, 302, 303, 307):
                event.addresponse(u'That expands to: %s', e.hdrs['location'])
                return
            raise

        event.addresponse(u"No redirect")
示例#6
0
文件: core.py 项目: shoosen/ibid
class Ignore(Processor):

    priority = -1500
    addressed = False
    event_types = (u'message', u'action', u'notice')

    nicks = ListOption('ignore', 'List of nicks to ignore', [])

    @handler
    def handle_ignore(self, event):
        for who in self.nicks:
            if event.sender['nick'] == who:
                event.processed = True
示例#7
0
文件: silc.py 项目: shoosen/ibid
class SourceFactory(IbidSourceFactory):

    auth = ('implicit', )
    supports = ('action', 'topic')

    server = Option('server', 'Server hostname')
    port = IntOption('port', 'Server port number', 706)
    nick = Option('nick', 'Nick', ibid.config['botname'])
    channels = ListOption('channels', 'Channels to autojoin', [])
    realname = Option('realname', 'Real Name', ibid.config['botname'])
    public_key = Option('public_key', 'Filename of public key', 'silc.pub')
    private_key = Option('private_key', 'Filename of private key', 'silc.prv')
    max_public_message_length = IntOption('max_public_message_length',
                                          'Maximum length of public messages',
                                          512)

    def __init__(self, name):
        IbidSourceFactory.__init__(self, name)
        self.log = logging.getLogger('source.%s' % self.name)
        pub = join(ibid.options['base'], self.public_key)
        prv = join(ibid.options['base'], self.private_key)
        if not exists(pub) and not exists(prv):
            keys = create_key_pair(pub, prv, passphrase='')
        else:
            keys = load_key_pair(pub, prv, passphrase='')
        self.client = SilcBot(keys, self.nick, self.nick, self.realname, self)

    def run_one(self):
        self.client.run_one()

    def setServiceParent(self, service):
        self.s = internet.TimerService(0.2, self.run_one)
        if service is None:
            self.s.startService()
        else:
            self.s.setServiceParent(service)

    def disconnect(self):
        self.client.disconnect()
        return True

    def url(self):
        return u'silc://%s@%s:%s' % (self.nick, self.server, self.port)

    def logging_name(self, identity):
        return self.client.logging_name(identity)

    def truncation_point(self, response, event=None):
        if response.get('target', None) in self.client.channels:
            return self.max_public_message_length
        return None
示例#8
0
class SourceFactory(IbidSourceFactory):

    auth = ('implicit', )
    supports = ('action', 'multiline', 'topic')

    subdomain = Option('subdomain', 'Campfire subdomain')
    secure = BoolOption('secure', 'Use https (paid accounts only)', False)
    token = Option('token', 'Campfire token')
    rooms = ListOption('rooms', 'Rooms to join', [])
    keepalive_timeout = IntOption(
        'keepalive_timeout', 'Stream keepalive timeout. '
        'Campfire sends a keepalive every <5 seconds', 30)

    def __init__(self, name):
        super(SourceFactory, self).__init__(name)
        self.log = logging.getLogger('source.%s' % self.name)
        self.client = CampfireBot(self)

    def setServiceParent(self, service):
        self.client.connect()

    def disconnect(self):
        self.client.disconnect()
        return True

    def url(self):
        protocol = self.secure and 'https' or 'http'
        return '%s://%s.campfirenow.com/' % (protocol, self.subdomain)

    def send(self, response):
        return self.client.send(response)

    def join(self, room_name):
        return self.client.join(room_name)

    def leave(self, room_name):
        return self.client.leave(room_name)

    def truncation_point(self, response, event=None):
        return None
示例#9
0
class Set(Processor):
    usage = u'<subject>(++|--|==| ftw| ftl) [[reason]]'
    features = ('karma', )

    # Clashes with morse & math
    priority = 510

    permission = u'karma'

    increase = ListOption('increase',
                          'Suffixes which indicate increased karma',
                          ('++', ' ftw'))
    decrease = ListOption('decrease',
                          'Suffixes which indicate decreased karma',
                          ('--', ' ftl'))
    neutral = ListOption('neutral', 'Suffixes which indicate neutral karma',
                         ('==', ))
    reply = BoolOption('reply', 'Acknowledge karma changes', False)
    public = BoolOption('public', 'Only allow karma changes in public', True)
    ignore = ListOption('ignore', 'Karma subjects to silently ignore', ())
    importance = IntOption(
        'importance', 'Threshold for number of changes after'
        " which a karma won't be forgotten", 0)

    def setup(self):
        # When not addressed, match karma changes in any text
        if self.addressed:
            matchpat = r'^(.+?)\s*(%s)\s*(?:[[{(]+\s*(.+?)\s*[\]})]+)?$'
        else:
            matchpat = r'(\S*\w\S*)(%s)(?:$|[\s,;\.\?!])'

        self.increase_reg = self.regex_tokens(self.increase)
        self.decrease_reg = self.regex_tokens(self.decrease)
        self.neutral_reg = self.regex_tokens(self.neutral)

        self.set.im_func.pattern = re.compile(
            matchpat %
            '|'.join(self.increase_reg + self.decrease_reg + self.neutral_reg),
            re.I | re.UNICODE | re.DOTALL)

    def regex_tokens(self, tokens):
        """ Turn configured tokens into regex versions """
        return [re.escape(t).replace(r'\ ', r'\s+') for t in tokens]

    def match_operators(self, roperators, adjust):
        return any(re.match(r, adjust) for r in roperators)

    @handler
    @authorise(fallthrough=False)
    def set(self, event, subject, adjust, reason=None):
        if reason is None:
            reason = event['message']['clean']

        if self.public and not event.public:
            event.addresponse(u'Karma must be done in public')
            return

        if subject.lower() in self.ignore:
            return

        karma = event.session.query(Karma).filter_by(subject=subject).first()
        if not karma:
            karma = Karma(subject)

        if self.match_operators(self.increase_reg, adjust.lower()):
            if subject.lower() == event.sender['nick'].lower():
                event.addresponse(u"You can't karma yourself!")
                return
            karma.changes += 1
            karma.value += 1
            change = u'Increased'
        elif self.match_operators(self.decrease_reg, adjust.lower()):
            karma.changes += 1
            karma.value -= 1
            change = u'Decreased'
        else:
            karma.changes += 2
            change = u'Increased and decreased'

        if karma.value == 0 and karma.changes <= self.importance:
            change = u'Forgotten (unimportant)'

            event.session.delete(karma)
        else:
            event.session.save_or_update(karma)
        event.session.commit()

        log.info(u"%s karma for '%s' by %s/%s (%s) because: %s", change,
                 subject, event.account, event.identity,
                 event.sender['connection'], reason)

        if self.reply:
            event.addresponse(
                u'%(subject)s now has %(value)s %(points)s of karma', {
                    'subject': subject,
                    'value': karma.value,
                    'points': plural(karma.value, "point", "points"),
                })
        else:
            event.processed = True
示例#10
0
文件: smtp.py 项目: shoosen/ibid
class SourceFactory(IbidSourceFactory, smtp.SMTPFactory):

    supports = ('multiline',)

    port = IntOption('port', 'Port number to listen on', 10025)
    address = Option('address', 'Email address to accept messages for and send from', 'ibid@localhost')
    accept = ListOption('accept', 'Email addresses to accept messages for', [])
    relayhost = Option('relayhost', 'SMTP server to relay outgoing messages to')

    def __init__(self, name):
        IbidSourceFactory.__init__(self, name)
        self.log = logging.getLogger('source.%s' % name)
        self.delivery = IbidDelivery(self)

    def buildProtocol(self, addr):
        p = smtp.SMTPFactory.buildProtocol(self, addr)
        p.delivery = self.delivery
        return p

    def setServiceParent(self, service):
        self.service = service
        if service:
            internet.TCPServer(self.port, self).setServiceParent(service)
        else:
            reactor.listenTCP(self.port, self)

    def url(self):
        return u'mailto:%s' % (self.address,)

    def respond(self, event):
        messages = {}
        for response in event.responses:
            if response['target'] not in messages:
                messages[response['target']] = response
            else:
                messages[response['target']]['reply'] += '\n' + response['reply']

        for message in messages.values():
            if 'subject' not in message:
                message['Subject'] = 'Re: ' + event['subject']
            if 'message-id' in event.headers:
                response['In-Reply-To'] = event.headers['message-id']
                if 'references' in event.headers:
                    response['References'] = '%(references)s %(message-id)s' % event.headers
                elif 'in-reply-to' in event.headers:
                    response['References'] = '%(in-reply-to)s %(message-id)s' % event.headers
                else:
                    response['References'] = '%(message-id)s' % event.headers

            self.send(message)

    def send(self, response):
        message = response['reply']
        response['To'] = response['target']
        response['Date'] = smtp.rfc822date()
        if 'Subject' not in response:
            response['Subject'] = 'Message from %s' % ibid.config['botname']
        response['Content-Type'] = 'text/plain; charset=utf-8'

        del response['target']
        del response['source']
        del response['reply']

        body = ''
        for header, value in response.items():
            body += '%s: %s\n' % (header, value)
        body += '\n'
        body += message

        port = ':' in self.relayhost and int(self.relayhost.split(':')[1]) or 25
        smtp.sendmail(self.relayhost.split(':')[0], self.address, response['To'], body.encode('utf-8'), port=port)
        self.log.debug(u"Sent email to %s: %s", response['To'], response['Subject'])
示例#11
0
文件: irc.py 项目: rsnyman/ibid
class SourceFactory(protocol.ReconnectingClientFactory, IbidSourceFactory):
    protocol = Ircbot

    auth = ('hostmask', 'nickserv')
    supports = ('action', 'notice', 'topic', 'channel key')

    port = IntOption('port', 'Server port number', 6667)
    ssl = BoolOption('ssl', 'Use SSL', False)
    server = Option('server', 'Server hostname')
    nick = Option('nick', 'IRC nick', ibid.config['botname'])
    realname = Option('realname', 'Full Name', ibid.config['botname'])
    password = Option('password', 'Connection password', None)
    username = Option('username', 'Local username', None)
    modes = Option('modes', 'User modes to set')
    channels = ListOption('channels', 'Channels to autojoin', [])
    ping_interval = FloatOption('ping_interval',
                                'Seconds idle before sending a PING', 60)
    pong_timeout = FloatOption('pong_timeout', 'Seconds to wait for PONG', 300)
    # ReconnectingClient uses this:
    maxDelay = IntOption('max_delay',
                         'Max seconds to wait inbetween reconnects', 900)
    factor = FloatOption('delay_factor',
                         'Factor to multiply delay inbetween reconnects by', 2)

    def __init__(self, name):
        IbidSourceFactory.__init__(self, name)
        self._auth = {}
        self._auth_ticket = 0
        self._auth_ticket_lock = Lock()
        self.log = logging.getLogger('source.%s' % self.name)

    def setServiceParent(self, service):
        if self.ssl:
            sslctx = ssl.ClientContextFactory()
            if service:
                internet.SSLClient(self.server, self.port, self,
                                   sslctx).setServiceParent(service)
            else:
                reactor.connectSSL(self.server, self.port, self, sslctx)
        else:
            if service:
                internet.TCPClient(self.server, self.port,
                                   self).setServiceParent(service)
            else:
                reactor.connectTCP(self.server, self.port, self)

    def connect(self):
        return self.setServiceParent(None)

    def disconnect(self):
        self.stopTrying()
        self.stopFactory()
        if hasattr(self, 'proto'):
            self.proto.transport.loseConnection()
        return True

    def join(self, channel, key=None):
        return self.proto.join(channel, key)

    def leave(self, channel):
        return self.proto.leave(channel)

    def change_nick(self, nick):
        return self.proto.setNick(nick.encode('utf-8'))

    def send(self, response):
        return self.proto.send(response)

    def logging_name(self, identity):
        if identity is None:
            return u''
        return identity.split(u'!')[0]

    def truncation_point(self, response, event=None):
        target = response['target'].split('!')[0]
        raw_target = target.encode('utf-8')
        if hasattr(self.proto, 'hostmask'):
            hostmask_len = len(self.proto.hostmask)
        else:
            hostmask_len = 50

        # max = 512 - len(':' + hostmask + ' ' + command + ' ' + target + ' :\r\n')

        cmds = {
            'notice': len('NOTICE'),
            'topic': len('TOPIC'),
            'action': len('PRIVMSG\001ACTION \001')
        }
        for cmd, command_len in cmds.items():
            if response.get(cmd, False):
                break
        else:
            command_len = len('PRIVMSG')

        return 505 - command_len - len(raw_target) - hostmask_len

    def url(self):
        return u'irc://%s@%s:%s' % (self.nick, self.server, self.port)

    def auth_hostmask(self, event, credential=None):
        for credential in event.session.query(Credential) \
                .filter_by(method=u'hostmask', account_id=event.account) \
                .filter(or_(Credential.source == event.source,
                            Credential.source == None)) \
                .all():
            if fnmatch(event.sender['connection'], credential.credential):
                return True

    def auth_nickserv(self, event, credential):
        self._auth_ticket_lock.acquire()
        self._auth_ticket += 1
        ticket = self._auth_ticket
        self._auth_ticket_lock.release()

        def callback(result):
            self._auth[ticket] = result

        reactor.callFromThread(self.proto.authenticate, event.sender['nick'],
                               callback)
        # We block in the plugin thread for up to this long, waiting for
        # NickServ to reply
        wait = 15
        for i in xrange(wait * 10):
            if ticket in self._auth:
                break
            sleep(0.1)

        if ticket in self._auth:
            result = self._auth[ticket]
            del self._auth[ticket]
            return result
示例#12
0
文件: log.py 项目: shoosen/ibid
class Log(Processor):

    addressed = False
    processed = True
    event_types = (u'message', u'state', u'action', u'notice')
    priority = 1900

    log = Option('log', 'Log file to log messages to. Can contain substitutions: source, channel, year, month, day',
            'logs/%(year)d/%(month)02d/%(source)s/%(channel)s.log')

    timestamp_format = Option('timestamp_format', 'Format to substitute %(timestamp)s with', '%Y-%m-%d %H:%M:%S%z')
    date_utc = BoolOption('date_utc', 'Log with UTC timestamps', False)

    message_format = Option('message_format', 'Format string for messages',
            u'%(timestamp)s <%(sender_nick)s> %(message)s')
    action_format = Option('action_format', 'Format string for actions',
            u'%(timestamp)s * %(sender_nick)s %(message)s')
    notice_format = Option('notice_format', 'Format string for notices',
            u'%(timestamp)s -%(sender_nick)s- %(message)s')
    presence_format = Option('presence_format', 'Format string for presence events',
            u'%(timestamp)s %(sender_nick)s (%(sender_connection)s) is now %(state)s')
    rename_format = Option('rename_format', 'Format string for rename events',
            u'%(timestamp)s %(sender_nick)s (%(sender_connection)s) has renamed to %(new_nick)s')

    public_logs = ListOption('public_logs',
            u'List of source:channel globs for channels which should have public logs',
            [])
    public_mode = Option('public_mode',
            u'File Permissions mode for public channels, in octal', '644')
    private_mode = Option('private_mode',
            u'File Permissions mode for private chats, in octal', '640')
    dir_mode = Option('dir_mode',
            u'Directory Permissions mode, in octal', '755')

    fd_cache = IntOption('fd_cache', 'Number of log files to keep open.', 5)

    lock = Lock()
    logs = WeakValueDictionary()
    # Ensures that recently used FDs are still available in logs:
    recent_logs = []

    def setup(self):
        sources = list(set(ibid.config.sources.keys())
                       | set(ibid.sources.keys()))
        for glob in self.public_logs:
            if u':' not in glob:
                log.warning(u"public_logs configuration values must follow the "
                            u"format source:channel. \"%s\" doesn't contain a "
                            u"colon.", glob)
                continue
            source_glob = glob.split(u':', 1)[0]
            if not fnmatch.filter(sources, source_glob):
                log.warning(u'public_logs includes "%s", but there is no '
                            u'configured source matching "%s"',
                            glob, source_glob)

    def get_logfile(self, event):
        self.lock.acquire()
        try:
            when = event.time
            if not self.date_utc:
                when = when.replace(tzinfo=tzutc()).astimezone(tzlocal())

            if event.channel is not None:
                channel = ibid.sources[event.source].logging_name(event.channel)
            else:
                channel = ibid.sources[event.source].logging_name(event.sender['id'])
            filename = self.log % {
                    'source': event.source.replace('/', '-'),
                    'channel': channel.replace('/', '-'),
                    'year': when.year,
                    'month': when.month,
                    'day': when.day,
                    'hour': when.hour,
                    'minute': when.minute,
                    'second': when.second,
            }
            filename = join(ibid.options['base'], expanduser(filename))
            log = self.logs.get(filename, None)
            if log is None:
                try:
                    makedirs(dirname(filename), int(self.dir_mode, 8))
                except OSError, e:
                    if e.errno != EEXIST:
                        raise e

                log = open(filename, 'a')
                self.logs[filename] = log

                for glob in self.public_logs:
                    if u':' not in glob:
                        continue
                    source_glob, channel_glob = glob.split(u':', 1)
                    if (fnmatch.fnmatch(event.source, source_glob)
                            and fnmatch.fnmatch(channel, channel_glob)):
                        chmod(filename, int(self.public_mode, 8))
                        break
                else:
                    chmod(filename, int(self.private_mode, 8))
            else:
示例#13
0
文件: factoid.py 项目: rsnyman/ibid
class Set(Processor):
    usage = u"""<name> (<verb>|=<verb>=) [also] <value>
    last set factoid"""
    features = ('factoid', )

    interrogatives = ListOption('interrogatives', 'Question words to strip',
                                default_interrogatives)
    verbs = ListOption('verbs', 'Verbs that split name from value',
                       default_verbs)

    priority = 800
    permission = u'factoid'
    last_set_factoid = None

    def setup(self):
        self.set_factoid.im_func.pattern = re.compile(
            r'^(no[,.: ]\s*)?(.+?)\s+(also\s+)?(?:=(\S+)=)?(?(4)|(%s))(\s+also)?\s+((?(3).+|(?!.*=\S+=).+))$'
            % '|'.join(self.verbs), re.I)
        self.set_factoid.im_func.message_version = 'deaddressed'

    @handler
    @authorise(fallthrough=False)
    def set_factoid(self, event, correction, name, addition1, verb1, verb2,
                    addition2, value):
        verb = verb1 or verb2
        addition = addition1 or addition2

        name = strip_name(name)

        if name == u'':
            event.addresponse(u"Sorry, I'm not interested in empty factoids")
            return

        if name.lower() in self.interrogatives:
            event.addresponse(
                choice((
                    u"I'm afraid I have no idea",
                    u"Not a clue, sorry",
                    u"Erk, dunno",
                )))
            return

        factoid = event.session.query(Factoid).join(Factoid.names)\
                .filter(FactoidName.name==escape_name(name)).first()
        if factoid:
            if correction:
                identities = get_identities(event)
                if not auth_responses(event, u'factoidadmin') and len(
                        filter(lambda x: x.identity_id not in identities,
                               factoid.values)) > 0:
                    return
                for fvalue in factoid.values:
                    event.session.delete(fvalue)
            elif not addition:
                event.addresponse(u'I already know stuff about %s', name)
                return
        else:
            factoid = Factoid()
            fname = FactoidName(unicode(name), event.identity)
            factoid.names.append(fname)
            event.session.add(factoid)
            event.session.flush()
            log.info(u"Creating factoid %s with name '%s' by %s", factoid.id,
                     fname.name, event.identity)

        if not reply_re.match(value) and not action_re.match(value):
            value = '%s %s' % (verb, value)
        fvalue = FactoidValue(unicode(value), event.identity)
        factoid.values.append(fvalue)
        event.session.add(factoid)
        event.session.commit()
        self.last_set_factoid = factoid.names[0].name
        log.info(u"Added value '%s' to factoid %s (%s) by %s/%s (%s)",
                 fvalue.value, factoid.id, factoid.names[0].name,
                 event.account, event.identity, event.sender['connection'])
        event.addresponse(
            choice((
                u'If you say so',
                u'One learns a new thing every day',
                u"I'll remember that",
                u'Got it',
            )))

    @match(r'^(?:last\s+set\s+factoid|what\s+did\s+\S+\s+just\s+set)$')
    def last_set(self, event):
        if self.last_set_factoid is None:
            event.addresponse(u'Sorry, nobody has taught me anything recently')
        else:
            event.addresponse(u'It was: %s', self.last_set_factoid)
示例#14
0
文件: fun.py 项目: vhata/ibid
class Insult(Processor):
    usage = u"""(flame | insult) <person>
    (swear | cuss | explete) [at <person>]"""
    features = ('insult', )

    adjectives = ListOption('adjectives', 'List of adjectives', (
        u'acidic',
        u'antique',
        u'artless',
        u'base-court',
        u'bat-fowling',
        u'bawdy',
        u'beef-witted',
        u'beetle-headed',
        u'beslubbering',
        u'boil-brained',
        u'bootless',
        u'churlish',
        u'clapper-clawed',
        u'clay-brained',
        u'clouted',
        u'cockered',
        u'common-kissing',
        u'contemptible',
        u'coughed-up',
        u'craven',
        u'crook-pated',
        u'culturally-unsound',
        u'currish',
        u'dankish',
        u'decayed',
        u'despicable',
        u'dismal-dreaming',
        u'dissembling',
        u'dizzy-eyed',
        u'doghearted',
        u'dread-bolted',
        u'droning',
        u'earth-vexing',
        u'egg-sucking',
        u'elf-skinned',
        u'errant',
        u'evil',
        u'fat-kidneyed',
        u'fawning',
        u'fen-sucked',
        u'fermented',
        u'festering',
        u'flap-mouthed',
        u'fly-bitten',
        u'fobbing',
        u'folly-fallen',
        u'fool-born',
        u'foul',
        u'frothy',
        u'froward',
        u'full-gorged',
        u'fulminating',
        u'gleeking',
        u'goatish',
        u'gorbellied',
        u'guts-griping',
        u'hacked-up',
        u'halfbaked',
        u'half-faced',
        u'hasty-witted',
        u'headless',
        u'hedge-born',
        u'hell-hated',
        u'horn-beat',
        u'hugger-muggered',
        u'humid',
        u'idle-headed',
        u'ill-borne',
        u'ill-breeding',
        u'ill-nurtured',
        u'imp-bladdereddle-headed',
        u'impertinent',
        u'impure',
        u'industrial',
        u'inept',
        u'infected',
        u'infectious',
        u'inferior',
        u'it-fowling',
        u'jarring',
        u'knotty-pated',
        u'left-over',
        u'lewd-minded',
        u'loggerheaded',
        u'low-quality',
        u'lumpish',
        u'malodorous',
        u'malt-wormy',
        u'mammering',
        u'mangled',
        u'measled',
        u'mewling',
        u'milk-livered',
        u'motley-mind',
        u'motley-minded',
        u'off-color',
        u'onion-eyed',
        u'paunchy',
        u'penguin-molesting',
        u'petrified',
        u'pickled',
        u'pignutted',
        u'plume-plucked',
        u'pointy-nosed',
        u'porous',
        u'pottle-deep',
        u'pox-marked',
        u'pribbling',
        u'puking',
        u'puny',
        u'railing',
        u'rank',
        u'reeky',
        u'reeling-ripe',
        u'roguish',
        u'rough-hewn',
        u'rude-growing',
        u'rude-snouted',
        u'rump-fed',
        u'ruttish',
        u'salty',
        u'saucy',
        u'saucyspleened',
        u'sausage-snorfling',
        u'shard-borne',
        u'sheep-biting',
        u'spam-sucking',
        u'spleeny',
        u'spongy',
        u'spur-galled',
        u'squishy',
        u'surly',
        u'swag-bellied',
        u'tardy-gaited',
        u'tastless',
        u'tempestuous',
        u'tepid',
        u'thick',
        u'tickle-brained',
        u'toad-spotted',
        u'tofu-nibbling',
        u'tottering',
        u'uninspiring',
        u'unintelligent',
        u'unmuzzled',
        u'unoriginal',
        u'urchin-snouted',
        u'vain',
        u'vapid',
        u'vassal-willed',
        u'venomed',
        u'villainous',
        u'warped',
        u'wayward',
        u'weasel-smelling',
        u'weather-bitten',
        u'weedy',
        u'wretched',
        u'yeasty',
    ))

    collections = ListOption('collections', 'List of collective nouns', (
        u'accumulation',
        u'ass-full',
        u'assload',
        u'bag',
        u'bucket',
        u'coagulation',
        u'enema-bucketful',
        u'gob',
        u'half-mouthful',
        u'heap',
        u'mass',
        u'mound',
        u'ooze',
        u'petrification',
        u'pile',
        u'plate',
        u'puddle',
        u'quart',
        u'stack',
        u'thimbleful',
        u'tongueful',
    ))

    nouns = ListOption('nouns', u'List of singular nouns', (
        u'apple-john',
        u'baggage',
        u'barnacle',
        u'bladder',
        u'boar-pig',
        u'bugbear',
        u'bum-bailey',
        u'canker-blossom',
        u'clack-dish',
        u'clotpole',
        u'coxcomb',
        u'codpiece',
        u'death-token',
        u'dewberry',
        u'flap-dragon',
        u'flax-wench',
        u'flirt-gill',
        u'foot-licker',
        u'fustilarian',
        u'giglet',
        u'gudgeon',
        u'haggard',
        u'harpy',
        u'hedge-pig',
        u'horn-beast',
        u'hugger-mugger',
        u'jolthead',
        u'lewdster',
        u'lout',
        u'maggot-pie',
        u'malt-worm',
        u'mammet',
        u'measle',
        u'minnow',
        u'miscreant',
        u'moldwarp',
        u'mumble-news',
        u'nut-hook',
        u'pigeon-egg',
        u'pignut',
        u'puttock',
        u'pumpion',
        u'ratsbane',
        u'scut',
        u'skainsmate',
        u'strumpet',
        u'varlet',
        u'vassal',
        u'whey-face',
        u'wagtail',
    ))

    plnouns = ListOption('plnouns', u'List of plural nouns', (
        u'anal warts',
        u'armadillo snouts',
        u'bat toenails',
        u'bug spit',
        u'buzzard gizzards',
        u'cat bladders',
        u'cat hair',
        u'cat-hair-balls',
        u'chicken piss',
        u'cold sores',
        u'craptacular carpet droppings',
        u'dog balls',
        u'dog vomit',
        u'dung',
        u'eel ooze',
        u'entrails',
        u"fat-woman's stomach-bile",
        u'fish heads',
        u'guano',
        u'gunk',
        u'jizzum',
        u'pods',
        u'pond scum',
        u'poop',
        u'poopy',
        u'pus',
        u'rat-farts',
        u'rat retch',
        u'red dye number-9',
        u'seagull puke',
        u'slurpee-backwash',
        u'snake assholes',
        u'snake bait',
        u'snake snot',
        u'squirrel guts',
        u'Stimpy-drool',
        u'Sun IPC manuals',
        u'toxic waste',
        u'urine samples',
        u'waffle-house grits',
        u'yoo-hoo',
    ))

    @match(r'^(?:insult|flame)\s+(.+)$')
    def insult(self, event, insultee):
        articleadj = choice(self.adjectives)
        articleadj = (articleadj[0] in u'aehiou' and u'an '
                      or u'a ') + articleadj

        event.addresponse(choice((
            u'%(insultee)s, thou %(adj1)s, %(adj2)s %(noun)s',
            u'%(insultee)s is nothing but %(articleadj)s %(collection)s of %(adj1)s %(plnoun)s',
        )), {
            'insultee': insultee,
            'adj1': choice(self.adjectives),
            'adj2': choice(self.adjectives),
            'articleadj': articleadj,
            'collection': choice(self.collections),
            'noun': choice(self.nouns),
            'plnoun': choice(self.plnouns),
        },
                          address=False)

    loneadjectives = ListOption('loneadjectives',
                                'List of stand-alone adjectives for swearing',
                                (
                                    'bloody',
                                    'damn',
                                    'f*****g',
                                    's******g',
                                    'sodding',
                                    'crapping',
                                    'wanking',
                                    'buggering',
                                ))

    swearadjectives = ListOption(
        'swearadjectives', 'List of adjectives to be combined with swearnouns',
        (
            'reaming',
            'lapping',
            'eating',
            'sucking',
            'vokken',
            'kak',
            'donder',
            'bliksem',
            'f*****g',
            's******g',
            'sodding',
            'crapping',
            'wanking',
            'buggering',
        ))

    swearnouns = ListOption(
        'swearnouns', 'List of nounes to be comined with swearadjectives', (
            'shit',
            'c**t',
            'hell',
            'mother',
            'god',
            'maggot',
            'father',
            'crap',
            'ball',
            'w***e',
            'goat',
            'dick',
            'c**k',
            'pile',
            'bugger',
            'poes',
            'hoer',
            'kakrooker',
            'ma',
            'pa',
            'naiier',
            'kak',
            'bliksem',
            'vokker',
            'kakrooker',
        ))

    swearlength = IntOption('swearlength',
                            'Number of expletives to swear with', 15)

    @match(r'^(?:swear|cuss|explete)(?:\s+at\s+(?:the\s+)?(.*))?$')
    def swear(self, event, insultee):
        swearage = []
        for i in range(self.swearlength):
            if random() > 0.7:
                swearage.append(choice(self.loneadjectives))
            else:
                swearage.append(
                    choice(self.swearnouns) + choice(self.swearadjectives))
        if insultee is not None:
            swearage.append(insultee)
        else:
            swearage.append(choice(self.swearnouns))

        event.addresponse(u' '.join(swearage) + u'!', address=False)
示例#15
0
class WriteFiglet(Processor):
    usage = u"""figlet <text> [in <font>]
    list figlet fonts [from <index>]"""
    feature = ('figlet',)

    max_width = IntOption('max_width', 'Maximum width for ascii output', 60)
    fonts_ = Option('fonts', 'Directory or Zip file containing figlet fonts',
                       '/usr/share/figlet')
    preferred_fonts = ListOption('preferred_fonts',
                                 'List of default figlet fonts',
                                 ('slant', 'letter'))

    def setup(self):
        self.fonts = None

    def _find_fonts(self):
        if self.fonts is not None:
            return

        if os.path.isdir(self.fonts_):
            self.fontstore = 'dir'
            fonts = listdir(self.fonts_)
        else:
            self.fontstore = 'zip'
            zip = ZipFile(self.fonts_)
            fonts = zip.namelist()
        fonts = fnmatch.filter(fonts, '*.[tf]lf')

        self.fonts = {}
        for font in fonts:
            font = os.path.splitext(font)[0]
            # Not all fonts are compatible with pyfiglet
            # (e.g. TLFs with colour):
            try:
                self._render('test', font)
            except FontNotFound:
                continue
            name = os.path.split(font)[-1]
            self.fonts[name] = font

    @match(r'^list\s+figlet\s+fonts(?:\s+from\s+(\d+))?$')
    def list_fonts(self, event, index):
        self._find_fonts()
        if index is None:
            index = 0
        index = int(index)
        if index >= len(self.fonts):
            event.addresponse(u'I wish I had that many fonts installed')
            return
        event.addresponse(unicode(', '.join(self.fonts.keys()[int(index):])))

    @match(r'^figlet\s+(.+?)(\s+in\s+(\S+))?$', 'deaddressed')
    def write(self, event, text, font_phrase, font):
        self._find_fonts()
        if font is not None and font not in self.fonts:
            text = '%s%s' % (text, font_phrase)
            font = None
        if font is None:
            if self.fonts:
                for font in self.preferred_fonts:
                    if font in self.fonts:
                        break
                else:
                    font = choice(self.fonts.keys())
            else:
                event.addresponse(u"I'm afraid I have no fonts available")
                return
        self._write(event, text, font)

    def _render(self, text, font):
        if self.fontstore == 'dir':
            figlet = Figlet(font=font, dir=self.fonts_)
        else:
            figlet = Figlet(font=font, zipfile=self.fonts_)
        return figlet.renderText(text)

    def _write(self, event, text, font):
        rendered = self._render(text, font).split('\n')
        while rendered and rendered[0].strip() == '':
            del rendered[0]
        while rendered and rendered[-1].strip() == '':
            del rendered[-1]
        if rendered and len(rendered[0]) > self.max_width:
            event.addresponse(
                    u"Sorry that's too long, nobody will be able to read it")
            return
        event.addresponse(unicode('\n'.join(rendered), 'utf-8'),
                          address=False, conflate=False)
示例#16
0
class Set(Processor):
    usage = u'<subject> (++|--|==|ftw|ftl) [[reason]]'
    feature = ('karma', )

    # Clashes with morse & math
    priority = 510

    permission = u'karma'

    increase = ListOption('increase',
                          'Suffixes which indicate increased karma',
                          ('++', 'ftw'))
    decrease = ListOption('decrease',
                          'Suffixes which indicate decreased karma',
                          ('--', 'ftl'))
    neutral = ListOption('neutral', 'Suffixes which indicate neutral karma',
                         ('==', ))
    reply = BoolOption('reply', 'Acknowledge karma changes', False)
    public = BoolOption('public', 'Only allow karma changes in public', True)
    ignore = ListOption('ignore', 'Karma subjects to silently ignore', ())
    importance = IntOption(
        'importance', 'Threshold for number of changes after'
        " which a karma won't be forgotten", 0)

    def setup(self):
        self.set.im_func.pattern = re.compile(
            r'^(.+?)\s*(%s)\s*(?:[[{(]+\s*(.+?)\s*[\]})]+)?$' % '|'.join(
                re.escape(token)
                for token in self.increase + self.decrease + self.neutral),
            re.I)

    @handler
    @authorise(fallthrough=False)
    def set(self, event, subject, adjust, reason):
        if self.public and not event.public:
            event.addresponse(u'Karma must be done in public')
            return

        if subject.lower() in self.ignore:
            return

        karma = event.session.query(Karma).filter_by(subject=subject).first()
        if not karma:
            karma = Karma(subject)

        if adjust.lower() in self.increase:
            if subject.lower() == event.sender['nick'].lower():
                event.addresponse(u"You can't karma yourself!")
                return
            karma.changes += 1
            karma.value += 1
            change = u'Increased'
        elif adjust.lower() in self.decrease:
            karma.changes += 1
            karma.value -= 1
            change = u'Decreased'
        else:
            karma.changes += 2
            change = u'Increased and decreased'

        if karma.value == 0 and karma.changes <= self.importance:
            change = u'Forgotten (unimportant)'

            event.session.delete(karma)
        else:
            event.session.save_or_update(karma)
        event.session.commit()

        log.info(u"%s karma for '%s' by %s/%s (%s) because: %s", change,
                 subject, event.account, event.identity,
                 event.sender['connection'], reason)

        if self.reply:
            event.addresponse(True)
        else:
            event.processed = True
示例#17
0
class DuelDraw(Processor):
    usage = u"""draw [my <weapon>]
    bam|pew|bang|kapow|pewpew|holyhandgrenadeofantioch"""

    feature = ('duel', )

    # Parameters for Processor:
    event_types = (u'message', u'action')

    addressed = BoolOption('addressed', 'Must the bot be addressed?', True)

    # Game configurables:
    weapons = DictOption(
        'weapons', 'Weapons that can be used: name: (chance, damage)', {
            u'bam': (0.75, 50),
            u'pew': (0.75, 50),
            u'fire': (0.75, 70),
            u'fires': (0.75, 70),
            u'bang': (0.75, 70),
            u'kapow': (0.75, 90),
            u'pewpew': (0.75, 110),
            u'holyhandgrenadeofantioch': (1.0, 200),
        })
    extremities = ListOption('extremities', u'Extremities that can be hit', (
        u'toe',
        u'foot',
        u'leg',
        u'thigh',
        u'finger',
        u'hand',
        u'arm',
        u'elbow',
        u'shoulder',
        u'ear',
        u'nose',
        u'stomach',
    ))
    vitals = ListOption('vitals', 'Vital parts of the body that can be hit', (
        u'head',
        u'groin',
        u'chest',
        u'heart',
        u'neck',
    ))

    draw_required = BoolOption('draw_required',
                               'Must you draw your weapon before firing?',
                               True)
    extratime = FloatOption(
        'extratime', 'How much more time to grant after every shot fired?',
        1.0)

    @match(r'^draws?(?:\s+h(?:is|er)\s+.*|\s+my\s+.*)?$')
    def draw(self, event):
        if (event.source, event.channel) not in duels:
            if event.get('addressed', False):
                event.addresponse(
                    choice((
                        u"We do not permit drawn weapons here",
                        u"You may only draw a weapon on the field of honour",
                    )))
            return

        duel = duels[(event.source, event.channel)]

        shooter = event.sender['nick']
        if shooter.lower() not in duel.names:
            event.addresponse(
                choice((
                    u"Spectators are not permitted to draw weapons",
                    u"Do you think you are %(fighter)s?",
                )), {'fighter': choice(duel.names.values())})
            return

        if not duel.started:
            event.addresponse(
                choice((
                    u"Now now, not so fast!",
                    u"Did I say go yet?",
                    u"Put that AWAY!",
                )))
            return

        duel.drawn[shooter.lower()] = True
        event.addresponse(True)

    def setup(self):
        self.fire.im_func.pattern = re.compile(
            r'^(%s)(?:[\s,.!:;].*)?$' % '|'.join(self.weapons.keys()),
            re.I | re.DOTALL)

    @handler
    def fire(self, event, weapon):
        shooter = event.sender['nick'].lower()
        if (event.source, event.channel) not in duels:
            return

        duel = duels[(event.source, event.channel)]

        if shooter not in duel.names:
            event.addresponse(
                choice((
                    u"You aren't in a war",
                    u'You are a non-combatant',
                    u'You are a spectator',
                )))
            return

        enemy = set(duel.names.keys())
        enemy.remove(shooter)
        enemy = enemy.pop()

        if self.draw_required and not duel.drawn[shooter]:
            recipient = shooter
        else:
            recipient = enemy

        if not duel.started or not duel.confirmed:
            if self.draw_required:
                message = choice((
                    u"%(shooter)s tried to escape his duel by shooting himself in the foot. The duel has been cancelled, but his honour is forfeit",
                    u"%(shooter)s shot himself while preparing for his duel. The funeral will be held on the weekend",
                ))
            elif not duel.started:
                message = choice((
                    u"FOUL! %(shooter)s fired before my mark. Just as well you didn't hit anything. I refuse to referee under these conditions",
                    u"FOUL! %(shooter)s injures %(enemy)s before the match started and is marched away in handcuffs",
                    u"FOUL! %(shooter)s killed %(enemy)s before the match started and was shot by the referee before he could hurt anyone else",
                ))
            else:
                message = choice((
                    u"FOUL! The duel is not yet confirmed. %(shooter)s is marched away in handcuffs",
                    u"FOUL! Arrest %(shooter)s! Firing a weapon within city limits is not permitted",
                ))
            event.addresponse(message, {
                'shooter': duel.names[shooter],
                'enemy': duel.names[enemy],
            },
                              address=False)
            del duels[(event.source, event.channel)]
            duel.stop()
            return

        chance, power = self.weapons[weapon.lower()]

        if random() < chance:
            damage = max(gauss(power, power / 2.0), 0)
            duel.hp[recipient] -= damage
            if duel.hp[recipient] <= 0.0:
                del duels[(event.source, event.channel)]
                duel.stop()
            else:
                duel.timeout_callback.delay(self.extratime)

            params = {
                'shooter': duel.names[shooter],
                'enemy': duel.names[enemy],
                'part': u'foot',
            }
            if shooter == recipient:
                message = u"TRAGEDY: %(shooter)s shoots before drawing his weapon. "
                if damage > 100.0:
                    message += choice((
                        u"The explosion killed him",
                        u"There was little left of him",
                    ))
                elif duel.hp[recipient] <= 0.0:
                    message += choice((
                        u"Combined with his other injuries, he didn't stand a chance",
                        u"He died during field surgery",
                    ))
                else:
                    message += choice((
                        u"Luckily, it was only a flesh wound",
                        u"He narrowly missed his femoral artery",
                    ))

            elif damage > 100.0:
                message = u'VICTORY: ' + choice((
                    u'%(shooter)s blows %(enemy)s away',
                    u'%(shooter)s destroys %(enemy)s',
                ))
            elif duel.hp[enemy] <= 0.0:
                message = u'VICTORY: ' + choice((
                    u'%(shooter)s kills %(enemy)s with a shot to the %(part)s',
                    u'%(shooter)s shoots %(enemy)s killing him with a fatal shot to the %(part)s',
                ))
                params['part'] = choice(self.vitals)
            else:
                message = choice((
                    u'%(shooter)s hits %(enemy)s in the %(part)s, wounding him',
                    u'%(shooter)s shoots %(enemy)s in the %(part)s, but %(enemy)s can still fight',
                ))
                params['part'] = choice(self.extremities)

            event.addresponse(message, params, address=False)

        elif shooter == recipient:
            event.addresponse(choice((
                u"%s forget to draw his weapon. Luckily he missed his foot",
                u"%s fires a holstered weapon. Luckily it only put a hole in his jacket",
                u"%s won't win at this rate. He forgot to draw before firing. He missed himself too",
            )),
                              duel.names[shooter],
                              address=False)
        else:
            event.addresponse(choice((u'%s misses', u'%s aims wide',
                                      u'%s is useless with a weapon')),
                              duel.names[shooter],
                              address=False)
示例#18
0
class DuelInitiate(Processor):
    usage = u"""
    I challenge <user> to a duel [over <something>]
    I demand satisfaction from <user> [over <something>]
    I throw the gauntlet down at <user>'s feet [over <something>]
    """

    feature = ('duel', )

    accept_timeout = FloatOption('accept_timeout',
                                 'How long do we wait for acceptance?', 60.0)
    start_delay = IntOption(
        'start_delay',
        'Time between acceptance and start of duel (rounded down to the highest minute)',
        30)
    timeout = FloatOption('timeout', 'How long is a duel on for', 15.0)

    happy_endings = ListOption('happy_endings', 'Both survive', (
        u'walk off into the sunset',
        u'go for a beer',
        u'call it quits',
    ))

    class Duel(object):
        def stop(self):
            for callback in ('cancel', 'start', 'timeout'):
                callback += '_callback'
                if hasattr(self, callback) and getattr(self,
                                                       callback).active():
                    getattr(self, callback).cancel()

    def shutdown(self):
        for duel in duels:
            duel.stop()

    @match(
        r'^(?:I\s+)throw\s+(?:down\s+(?:the|my)\s+gauntlet|(?:the|my)\s+gauntlet\s+down)\s+'
        r'at\s+(\S+?)(?:\'s\s+feet)?(?:\s+(?:over|because|for)\s+.+)?$')
    def initiate_gauntlet(self, event, recipient):
        self.initiate(event, recipient)

    @match(
        r'^(?:I\s+)?demand\s+satisfaction\s+from\s+(\S+)(?:\s+(?:over|because|for)\s+.+)?$'
    )
    def initiate_satisfaction(self, event, recipient):
        self.initiate(event, recipient)

    @match(
        r'^(?:I\s+)?challenge\s+(\S+)(?:\s+to\s+a\s+duel)?(?:\s+(?:over|because|for)\s+.+)?$'
    )
    def initiate(self, event, recipient):
        if not event.public:
            event.addresponse(
                choice((
                    u"All duels must take place in public places, by decree of the bot",
                    u"How do you expect to fight %(recipient)s, when he is not present?",
                    u"Your challenge must be made in public, Sir Knight",
                )), {'recipient': recipient})
            return

        if (event.source, event.channel) in duels:
            event.addresponse(
                choice((
                    u"We already have a war in here. Take your fight outside",
                    u"Isn't one fight enough? You may wait your turn",
                )))
            return

        aggressor = event.sender['nick']

        if recipient.lower() == aggressor.lower():
            # Yes I know schizophrenia isn't the same as DID, but this sounds better :P
            event.addresponse(
                choice((
                    u"Are you schizophrenic?",
                    u"Um, How exactly do you plan on fighting yourself?",
                )))
            return

        if recipient.lower() in [
                name.lower() for name in ibid.config.plugins['core']['names']
        ]:
            event.addresponse(
                choice((
                    u"I'm a peaceful bot",
                    u"The ref can't take part in the battle",
                    u"You just want me to die. No way",
                )))
            return

        duel = self.Duel()
        duels[(event.source, event.channel)] = duel

        duel.hp = {
            aggressor.lower(): 100.0,
            recipient.lower(): 100.0,
        }
        duel.names = {
            aggressor.lower(): aggressor,
            recipient.lower(): recipient,
        }
        duel.drawn = {
            aggressor.lower(): False,
            recipient.lower(): False,
        }

        duel.started = False
        duel.confirmed = False
        duel.aggressor = event.sender['nick'].lower()
        duel.recipient = recipient.lower()

        duel.cancel_callback = ibid.dispatcher.call_later(
            self.accept_timeout, self.cancel, event)

        event.addresponse(u'%(recipient)s: ' + choice((
            u"The gauntlet has been thrown at your feet. Do you accept?",
            u"You have been challenged. Do you accept?",
            u"%(aggressor)s wishes to meet you at dawn on the field of honour. Do you accept?",
        )), {
            'recipient': recipient,
            'aggressor': event.sender['nick'],
        },
                          address=False)

    def cancel(self, event):
        duel = duels[(event.source, event.channel)]
        del duels[(event.source, event.channel)]

        event.addresponse(
            choice((
                u"%(recipient)s appears to have fled the country during the night",
                u"%(recipient)s refuses to meet your challenge and accepts dishonour",
                u"Your challenge was not met. I suggest anger management counselling",
            )), {
                'recipient': duel.names[duel.recipient],
            })

    @match(
        r'^.*\b(?:ok|yes|I\s+do|sure|accept|hit\s+me|bite\s+me|i\'m\s+game|bring\s+it|yebo)\b.*$'
    )
    def confirm(self, event):
        if (event.source, event.channel) not in duels:
            return

        duel = duels[(event.source, event.channel)]

        confirmer = event.sender['nick'].lower()
        if confirmer not in duel.names or duel.confirmed or confirmer != duel.recipient:
            return

        # Correct capitalisation
        duel.names[confirmer] = event.sender['nick']

        duel.confirmed = True
        duel.cancel_callback.cancel()

        starttime = event.time + timedelta(seconds=self.start_delay +
                                           ((30 - event.time.second) % 30))
        starttime = starttime.replace(microsecond=0)
        delay = starttime - event.time
        delay = delay.seconds + (delay.microseconds / 10.**6)

        duel.start_callback = ibid.dispatcher.call_later(
            delay, self.start, event)

        event.addresponse(
            u"%(aggressor)s, %(recipient)s: "
            u"The duel shall begin on the stroke of %(starttime)s (in %(delay)s seconds). "
            + choice((
                u"You may clean your pistols.",
                u"Prepare yourselves.",
                u"Get ready",
            )), {
                'aggressor': duel.names[duel.aggressor],
                'recipient': duel.names[duel.recipient],
                'starttime': format_date(starttime, 'time'),
                'delay': (starttime - event.time).seconds,
            },
            address=False)

    def start(self, event):
        duel = duels[(event.source, event.channel)]

        duel.started = True
        duel.timeout_callback = ibid.dispatcher.call_later(
            self.timeout, self.end, event)

        event.addresponse(u'%s, %s: %s' % tuple(duel.names.values() + [
            choice((u'aaaand ... go!', u'5 ... 4 ... 3 ... 2 ... 1 ... fire!',
                    u'match on!', u'ready ... aim ... fire!'))
        ]),
                          address=False)

    def end(self, event):
        duel = duels[(event.source, event.channel)]
        del duels[(event.source, event.channel)]

        winner, loser = duel.names.keys()
        if duel.hp[winner] < duel.hp[loser]:
            winner, loser = loser, winner

        if duel.hp[loser] == 100.0:
            message = u"DRAW: %(winner)s and %(loser)s shake hands and %(ending)s"
        elif duel.hp[winner] < 50.0:
            message = u"DRAW: %(winner)s and %(loser)s bleed to death together"
        elif duel.hp[loser] < 50.0:
            message = u"VICTORY: %(loser)s bleeds to death"
        elif duel.hp[winner] < 100.0:
            message = u"DRAW: %(winner)s and %(loser)s hobble off together. Satisfaction is obtained"
        else:
            message = u"VICTORY: %(loser)s hobbles off while %(winner)s looks victorious"

        event.addresponse(message, {
            'loser': duel.names[loser],
            'winner': duel.names[winner],
            'ending': choice(self.happy_endings),
        },
                          address=False)
示例#19
0
文件: ascii.py 项目: vhata/ibid
class WriteFiglet(Processor):
    usage = u"""figlet <text> [in <font>]
    list figlet fonts [from <index>]"""
    features = ('figlet', )

    max_width = IntOption('max_width', 'Maximum width for ascii output', 60)
    preferred_fonts = ListOption('preferred_fonts',
                                 'List of default figlet fonts',
                                 ('slant', 'letter'))

    def setup(self):
        self.fonts = None

    def _find_fonts(self):
        if self.fonts is None:
            self.fonts = Figlet().getFonts()

    @match(r'^list\s+figlet\s+fonts(?:\s+from\s+(\d+))?$')
    def list_fonts(self, event, index):
        self._find_fonts()
        if index is None:
            index = 0
        index = int(index)
        if index >= len(self.fonts):
            event.addresponse(u'I wish I had that many fonts installed')
            return
        event.addresponse(unicode(', '.join(self.fonts[int(index):])))

    @match(r'^figlet\s+(.+?)(\s+in\s+(\S+))?$', 'deaddressed')
    def write(self, event, text, font_phrase, font):
        self._find_fonts()
        if font is not None and font not in self.fonts:
            text = '%s%s' % (text, font_phrase)
            font = None
        if font is None:
            if self.fonts:
                for font in self.preferred_fonts:
                    if font in self.fonts:
                        break
                else:
                    font = choice(self.fonts)
            else:
                event.addresponse(u"I'm afraid I have no fonts available")
                return
        self._write(event, text, font)

    def _render(self, text, font):
        figlet = Figlet(font=font)
        return figlet.renderText(text)

    def _write(self, event, text, font):
        rendered = self._render(text, font).split('\n')
        while rendered and rendered[0].strip() == '':
            del rendered[0]
        while rendered and rendered[-1].strip() == '':
            del rendered[-1]
        if rendered and len(rendered[0]) > self.max_width:
            event.addresponse(
                u"Sorry that's too long, nobody will be able to read it")
            return
        event.addresponse(unicode('\n'.join(rendered), 'utf-8'),
                          address=False,
                          conflate=False)