Ejemplo n.º 1
0
Archivo: ascii.py Proyecto: vhata/ibid
class DrawImage(Processor):
    usage = u'draw <url> [in colour] [width <width>] [height <height>]'
    features = ('draw-aa', )

    max_filesize = IntOption('max_filesize', 'Only request this many KiB', 200)
    def_height = IntOption('def_height', 'Default height for libaa output', 10)
    max_width = IntOption('max_width', 'Maximum width for ascii output', 60)
    max_height = IntOption('max_height', 'Maximum height for ascii output', 15)
    font_width = IntOption('font_width', 'Font width assumed for output', 6)
    font_height = IntOption('font_height', 'Font height assumed for output',
                            10)
    img2txt_bin = Option('img2txt_bin', 'libcaca img2txt binary to use',
                         'img2txt')

    def setup(self):
        if not file_in_path(self.img2txt_bin):
            raise Exception('Cannot locate img2txt executable')

    @match(
        r'^draw\s+(\S+\.\S+)(\s+in\s+colou?r)?(?:\s+w(?:idth)?\s+(\d+))?(?:\s+h(?:eight)\s+(\d+))?$'
    )
    def draw(self, event, url, colour, width, height):
        if not urlparse(url).netloc:
            url = 'http://' + url
        if urlparse(url).scheme == 'file':
            event.addresponse(u'Are you trying to haxor me?')
            return
        if not urlparse(url).path:
            url += '/'

        try:
            f = urlopen(iri_to_uri(url))
        except HTTPError, e:
            event.addresponse(u'Sorry, error fetching URL: %s',
                              BaseHTTPRequestHandler.responses[e.code][0])
            return
        except URLError:
            event.addresponse(u'Sorry, error fetching URL')
            return
Ejemplo n.º 2
0
class Retrieve(Processor):
    usage = u"""latest [ <count> ] articles from <name> [ starting at <number> ]
    article ( <number> | /<pattern>/ ) from <name>"""
    feature = ('feeds', )

    interval = IntOption('interval', 'Feed Poll interval (in seconds)', 300)

    @match(r'^(?:latest|last)\s+(?:(\d+)\s+)?articles\s+from\s+(.+?)'
           r'(?:\s+start(?:ing)?\s+(?:at\s+|from\s+)?(\d+))?$')
    def list(self, event, number, name, start):
        number = number and int(number) or 10
        start = start and int(start) or 0

        feed = event.session.query(Feed).filter_by(name=name).first()

        if not feed:
            event.addresponse(u"I don't know about the %s feed", name)
            return

        feed.update()
        if not feed.entries:
            event.addresponse(u"I can't find any articles in that feed")
            return

        articles = feed.entries[start:number + start]
        articles = [
            u'%s: "%s"' % (feed.entries.index(entry) + 1,
                           html2text_file(entry.title, None).strip())
            for entry in articles
        ]
        event.addresponse(u', '.join(articles))

    @match(r'^article\s+(?:(\d+)|/(.+?)/)\s+from\s+(.+?)$')
    def article(self, event, number, pattern, name):
        feed = event.session.query(Feed).filter_by(name=name).first()

        if not feed:
            event.addresponse(u"I don't know about the %s feed", name)
            return

        feed.update()
        if not feed.entries:
            event.addresponse(u"I can't access that feed")
            return
        article = None

        if number:
            if int(number) > len(feed.entries) or 1 > int(number):
                event.addresponse(u"That's old news dude")
                return
            article = feed.entries[int(number) - 1]

        else:
            pattern = re.compile(pattern, re.I)
            for entry in feed.entries:
                if pattern.search(entry.title):
                    article = entry
                    break

            if not article:
                event.addresponse(u'Are you making up news again?')
                return

        if 'summary' in article:
            summary = html2text_file(article.summary, None)
        else:
            if article.content[0].type in \
                    ('application/xhtml+xml', 'text/html'):
                summary = html2text_file(article.content[0].value, None)
            else:
                summary = article.content[0].value

        event.addresponse(
            u'"%(title)s" %(link)s : %(summary)s', {
                'title': html2text_file(article.title, None).strip(),
                'link': article.link,
                'summary': summary,
            })

    last_seen = {}

    @periodic(config_key='interval')
    def poll(self, event):
        feeds = event.session.query(Feed) \
                .filter(Feed.source != None) \
                .filter(Feed.target != None).all()

        for feed in feeds:
            try:
                feed.update(max_age=self.interval)
            except Exception, e:
                if isinstance(e, URLError):
                    log.warning(
                        u'Exception "%s" occured while polling '
                        u'feed %s from %s', e, feed, feed.url)
                else:
                    log.exception(
                        u'Exception "%s" occured while polling '
                        u'feed %s from %s', e, feed, feed.url)
                continue

            if not feed.entries:
                continue

            if feed.name not in self.last_seen:
                seen = {}
                for entry in feed.entries:
                    id = entry.get('id', entry.title)
                    seen[id] = entry.updated_parsed
                self.last_seen[feed.name] = seen
                continue

            old_seen = self.last_seen[feed.name]
            seen = {}
            for entry in reversed(feed.entries):
                id = entry.get('id', entry.title)
                seen[id] = entry.updated_parsed
                if entry.updated_parsed != old_seen.get(id):
                    event.addresponse(
                        u"%(status)s item in %(feed)s: %(title)s", {
                            'status': id in old_seen and u'Updated' or u'New',
                            'feed': feed.name,
                            'title': entry.title,
                        },
                        source=feed.source,
                        target=feed.target,
                        adress=False)
            self.last_seen[feed.name] = seen
Ejemplo n.º 3
0
class LogWatch(Processor):
    interval = IntOption('interval', 'Update Poll interval (in seconds)', 1)

    files = {}

    def incoming(self, target, filename, group, data):
        reply = u" == %(filename)s (%(group)s) == \n%(data)s" % {
            u'group': group,
            u'filename': filename,
            u'data': data.strip()
        }

        ibid.dispatcher.send({
            'reply': reply,
            'source': 'jabber',
            'target': target,
        })

    def setup(self):
        super(LogWatch, self).setup()

        # Start tailing all the setup alerts immediatly
        for section, group in groups.iteritems():
            for target in group['alerts']:
                for filename in group['files']:
                    self.add_tail(target, filename, filename)

    @periodic(config_key='interval', initial_delay=15)
    def update(self, event):
        for filename, obj in self.files.iteritems():
            # Try call the callback if we can
            data = obj['fileobj'].read()
            if data:
                for (target, group) in obj['events']:
                    self.incoming(target, filename, group, data)

            # Try get the latest file stats
            try:
                nstat = os.stat(filename)
            except:
                nstat = obj['fstat']

            # Check if the file changed, if so start from beginning again
            if nstat[stat.ST_DEV] != obj['fstat'][stat.ST_DEV] or nstat[
                    stat.ST_INO] != obj['fstat'][stat.ST_INO]:
                obj['fileobj'] = open(filename)
                obj['fstat'] = os.fstat(obj['fileobj'].fileno())

    def add_tail(self, target, filename, name):
        if not filename in self.files:
            fileobj = open(filename)
            fileobj.seek(0, 2)

            self.files[filename] = {
                'fileobj': fileobj,
                'fstat': os.fstat(fileobj.fileno()),
                'events': []
            }

        for event in self.files[filename]['events']:
            if event[0] == target: return False

        self.files[filename]['events'].append((target, name))
        return True

    def remove_tail(self, target, filename):
        if not filename in self.files:
            return

        self.files[filename]['events'] = [
            x for x in self.files[filename]['events'] if x[0] != target
        ]

    @match(r'^tail ([/^ ]+|"/[^"]*")$')
    def tail_file(self, event, filename):
        # Strip off quotes if needed
        if filename[0] == filename[-1] == '"':
            filename = filename[1:-1]

        if not os.path.exists(filename):
            event.addresponse(u"Sorry, I could not find %(filename)s",
                              {u'filename': filename})
        else:
            event.addresponse(u"Aiight, I'm watching %(filename)s for you.",
                              {u'filename': filename})
            self.add_tail(event['sender']['id'], filename, 'manual')

    @match(r'^tail ([a-z-]+)$')
    def tail_group(self, event, group):
        if group in groups:
            for filename in groups[group]['files']:
                self.add_tail(event['sender']['id'], filename, group)
            event.addresponse(u"Aiight, I'm watching %(group)s for you.",
                              {u'group': group})
        else:
            event.addresponse(u"Sorry, I could not find the group %(group)s",
                              {u'group': group})

    @match(r'^untail ([a-z-]+)$')
    def untail_group(self, event, group):
        if group in groups:
            for filename in groups[group]['files']:
                self.remove_tail(event['sender']['id'], filename)
            event.addresponse(u"Aiight, You're no longer tailing %(group)s.",
                              {u'group': group})
        else:
            event.addresponse(u"Sorry, I could not find the group %(group)s",
                              {u'group': group})
Ejemplo n.º 4
0
class BuildBot(Processor, RPC):
    usage = u'rebuild <branch> [ (revision|r) <number> ]'
    feature = ('buildbot', )
    autoload = False

    server = Option('server', 'Buildbot server hostname', 'localhost')
    status_port = IntOption('status_port', 'Buildbot server port number', 9988)
    change_port = IntOption('change_port', 'Buildbot server port number', 9989)
    source = Option('source', 'Source to send commit notifications to')
    channel = Option('channel', 'Channel to send commit notifications to')

    def __init__(self, name):
        Processor.__init__(self, name)
        RPC.__init__(self)

    def setup(self):
        self.status = pb.PBClientFactory()
        reactor.connectTCP(self.server, self.status_port, self.status)
        d = self.status.login(
            credentials.UsernamePassword('statusClient', 'clientpw'))
        d.addCallback(self.store_root, 'status')
        d.addCallback(
            lambda root: root.callRemote('subscribe', 'builds', 0, self))
        d.addErrback(self.exception)

        self.change = pb.PBClientFactory()
        reactor.connectTCP(self.server, self.change_port, self.change)
        d = self.change.login(
            credentials.UsernamePassword('change', 'changepw'))
        d.addCallback(self.store_root, 'change')
        d.addErrback(self.exception)

    def remote_built(self, branch, revision, person, result):
        reply = u"Build %s of %s triggered by %s: %s" % (revision, branch,
                                                         person, result)
        ibid.dispatcher.send({
            'reply': reply,
            'source': self.source,
            'target': self.channel
        })
        return True

    @match(r'^(?:re)?build\s+(.+?)(?:\s+(?:revision|r)?\s*(\d+))?$')
    def build(self, event, branch, revision):
        change = {
            'who': str(event.sender['nick']),
            'branch': str(branch),
            'files': [None],
            'revision': revision and str(revision) or '-1',
            'comments': 'Rebuild',
        }

        d = self.change_root.callRemote('addChange', change)
        d.addCallback(self.respond, event, True)
        d.addErrback(self.respond, event, False)
        event.processed = True

    def respond(self, rpc_response, event, result):
        ibid.dispatcher.send({
            'reply': result and 'Okay'
            or u"buildbot doesn't want to build :-(",
            'source': event.source,
            'target': event.channel
        })

    def store_root(self, root, type):
        setattr(self, '%s_root' % type, root)
        return root

    def exception(self, exception):
        print exception
        raise exception

    def remote_buildsetSubmitted(self, buildset):
        pass

    def remote_builderAdded(self, builderName, builder):
        pass

    def remote_builderChangedState(self, builderName, state, foo):
        pass

    def remote_buildStarted(self, builderName, build):
        print "Build %s started on %s" % (builderName, build)

    def remote_buildETAUpdate(self, build, ETA):
        pass

    def remote_stepStarted(self, build, step):
        pass

    def remote_stepTextChanged(self, build, step, text):
        pass

    def remote_stepText2Changed(self, build, step, text2):
        pass

    def remote_stepETAUpdate(self, build, step, ETA, expectations):
        pass

    def remote_logStarted(self, build, step, log):
        pass

    def remote_logChunk(self, build, step, log, channel, text):
        pass

    def remote_logFinished(self, build, step, log):
        pass

    def remote_stepFinished(self, build, step, results):
        pass

    def remote_buildFinished(self, builderName, build, results):
        print "Build %s finished on %s" % (builderName, build)

    def remote_builderRemoved(self, builderName):
        pass
Ejemplo n.º 5
0
class Bazaar(Processor, RPC):
    usage = u"""(last commit|commit <revno>) [to <repo>] [full]
    repositories"""
    features = ('bzr', )
    autoload = False

    repositories = DictOption('repositories',
                              'Dict of repositories names and URLs')
    interval = IntOption('interval',
                         'Interval inbetween checks for new revisions', 300)

    def __init__(self, name):
        self.log = logging.getLogger('plugins.bzr')
        Processor.__init__(self, name)
        RPC.__init__(self)

    def setup(self):
        self.branches = {}
        must_monitor = False
        for name, repository in self.repositories.items():
            try:
                self.branches[name.lower()] = Branch.open(repository['url'])
            except NotBranchError:
                self.log.error(u'%s is not a branch', repository)
                continue
            if repository.get('poll', 'False').lower() in ('yes', 'true'):
                must_monitor = True
        self.check.im_func.disabled = not must_monitor
        if must_monitor:
            self.seen_revisions = {}

    @match(r'^(?:repos|repositories)$')
    def handle_repositories(self, event):
        repositories = self.branches.keys()
        if repositories:
            event.addresponse(u'I know about: %s',
                              human_join(sorted(repositories)))
        else:
            event.addresponse(u"I don't know about any repositories")

    def remote_committed(self, repository, start, end=None):
        commits = self.get_commits(repository, start, end)
        repo = self.repositories[repository]
        for commit in commits:
            ibid.dispatcher.send({
                'reply': commit,
                'source': repo['source'],
                'target': repo['channel'],
            })

        return True

    @match(
        r'^(?:last\s+)?commit(?:\s+(\d+))?(?:(?:\s+to)?\s+(\S+?))?(\s+full)?$')
    def commit(self, event, revno, repository, full):

        revno = revno and int(revno) or None
        commits = self.get_commits(repository, revno, full=full)

        output = u''
        for commit in commits:
            if commit:
                output += commit.strip()
        event.addresponse(output, conflate=False)

    def get_commits(self, repository, start, end=None, full=None):
        branch = None
        if repository:
            repository = repository.lower()
            if repository not in self.branches:
                return None
            branch = self.branches[repository]

        if not branch:
            if len(self.branches) == 1:
                (repository, branch) = self.branches.items()[0]
            else:
                (repository,
                 branch) = sorted(self.branches.iteritems(),
                                  reverse=True,
                                  key=lambda (k, v): v.repository.get_revision(
                                      v.last_revision_info()[1]).timestamp)[0]

        if not start:
            start = branch.revision_id_to_revno(branch.last_revision())

        f = StringIO()
        log.show_log(branch,
                     LogFormatter(f, repository, branch, full),
                     start_revision=start,
                     end_revision=end or start)
        f.seek(0)
        commits = f.readlines()
        commits.reverse()
        return commits

    @handler
    def launchpad(self, event):
        if ibid.sources[event.source].type != 'smtp' \
                or 'X-Launchpad-Branch' not in event.headers:
            return

        event.processed = True

        if 'X-Launchpad-Branch' not in event.headers or 'X-Launchpad-Branch-Revision-Number' not in event.headers:
            return

        for name, repository in self.repositories.iteritems():
            if (event.headers['X-Launchpad-Branch'] == repository.get(
                    'lp_branch', None)):
                self.remote_committed(
                    name,
                    int(event.headers['X-Launchpad-Branch-Revision-Number']))

    @periodic(config_key='interval')
    def check(self, event):
        for name, repo in self.repositories.iteritems():
            if repo.get('poll', 'False').lower() not in ('yes', 'true'):
                continue
            branch = self.branches[name]
            lastrev = branch.last_revision()
            if name not in self.seen_revisions:
                self.seen_revisions[name] = lastrev
                continue
            if lastrev == self.seen_revisions[name]:
                continue

            try:
                commits = self.get_commits(name, None, False)
            except RevisionNotPresent:
                self.log.debug(
                    u"Got a RevisionNotPresent, hoping it won't be there next time..."
                )
                continue
            self.seen_revisions[name] = lastrev

            if commits:
                event.addresponse(unicode(commits[0].strip()),
                                  source=repo['source'],
                                  target=repo['channel'],
                                  address=False)
Ejemplo n.º 6
0
class Translate(Processor):
    usage = u"""translate (<phrase>|<url>) [from <language>] [to <language>]
    translation chain <phrase> [from <language>] [to <language>]"""

    feature = ('translate',)

    api_key = Option('api_key', 'Your Google API Key (optional)', None)
    referer = Option('referer', 'The referer string to use (API searches)', default_referer)
    dest_lang = Option('dest_lang', 'Destination language when none is specified', 'english')

    chain_length = IntOption('chain_length', 'Maximum length of translation chains', 10)

    lang_names = {'afrikaans':'af', 'albanian':'sq', 'arabic':'ar',
                  'belarusian':'be', 'bulgarian':'bg', 'catalan':'ca',
                  'chinese':'zh', 'chinese simplified':'zh-cn',
                  'chinese traditional':'zh-tw', 'croatian':'hr', 'czech':'cs',
                  'danish':'da', 'dutch':'nl', 'english':'en', 'estonian':'et',
                  'filipino':'tl', 'finnish':'fi', 'french':'fr',
                  'galacian':'gl', 'german':'de', 'greek':'el', 'hebrew':'iw',
                  'hindi':'hi', 'hungarian':'hu', 'icelandic':'is',
                  'indonesian':'id', 'irish':'ga', 'italian':'it',
                  'japanese':'ja', 'korean': 'ko', 'latvian':'lv',
                  'lithuanian':'lt', 'macedonian':'mk', 'malay':'ms',
                  'maltese':'mt', 'norwegian':'no', 'persian':'fa',
                  'polish':'pl', 'portuguese':'pt', 'romanian':'ro',
                  'russian': 'ru', 'serbian':'sr', 'slovak':'sk',
                  'slovenian':'sl', 'spanish':'es', 'swahili':'sw',
                  'swedish':'sv', 'thai':'th', 'turkish':'tr', 'ukrainian':'uk',
                  'uzbek': 'uz', 'vietnamese':'vi', 'welsh':'cy',
                  'yiddish': 'yi', 'haitian creole': 'ht'}

    alt_lang_names = {'simplified':'zh-CN', 'simplified chinese':'zh-CN',
                   'traditional':'zh-TW', 'traditional chinese':'zh-TW',
                   'bokmal':'no', 'norwegian bokmal':'no',
                   u'bokm\N{LATIN SMALL LETTER A WITH RING ABOVE}l':'no',
                   u'norwegian bokm\N{LATIN SMALL LETTER A WITH RING ABOVE}l':
                        'no',
                   'farsi': 'fa',
                   'haitian': 'ht', 'kreyol': 'ht'}

    LANG_REGEX = '|'.join(lang_names.keys() + lang_names.values() +
                            alt_lang_names.keys())

    @match(r'^(?:translation\s*)?languages$')
    def languages (self, event):
        event.addresponse(human_join(sorted(self.lang_names.keys())))

    @match(r'^translate\s+(.*?)(?:\s+from\s+(' + LANG_REGEX + r'))?'
            r'(?:\s+(?:in)?to\s+(' + LANG_REGEX + r'))?$')
    def translate (self, event, text, src_lang, dest_lang):
        dest_lang = self.language_code(dest_lang or self.dest_lang)
        src_lang = self.language_code(src_lang or '')

        if is_url(text):
            if urlparse(text).scheme in ('', 'http'):
                url = url_to_bytestring(text)
                query = {'sl': src_lang, 'tl': dest_lang, 'u': url}
                event.addresponse(u'http://translate.google.com/translate?' +
                                    urlencode(query))
            else:
                event.addresponse(u'I can only translate HTTP pages')
            return

        try:
            translated = self._translate(event, text, src_lang, dest_lang)[0]
            event.addresponse(translated)
        except TranslationException, e:
            event.addresponse(u"I couldn't translate that: %s.", unicode(e))
Ejemplo n.º 7
0
class HTTP(Processor):
    usage = u"""(get|head) <url>
    is <domain> (up|down)
    tell me when <domain|url> is up"""
    features = ('http',)
    priority = -10

    max_size = IntOption('max_size', u'Only request this many bytes', 2048)
    timeout = IntOption('timeout',
            u'Timeout for HTTP connections in seconds', 15)
    sites = DictOption('sites', u'Mapping of site names to domains', {})
    redirect_limit = IntOption('redirect_limit',
            u'Maximum number of http redirects to follow', 5)
    whensitup_delay = IntOption('whensitup_delay',
            u'Initial delay between whensitup attempts in seconds', 60)
    whensitup_factor = FloatOption('whensitup_factor',
            u'Factor to mutliply subsequent delays by for whensitup', 1.03)
    whensitup_maxdelay = IntOption('whensitup_maxdelay',
            u'Maximum delay between whensitup attempts in seconds', 30*60)
    whensitup_maxperiod = FloatOption('whensitup_maxperiod',
            u'Maximum period after which to stop checking the url '
            u'for whensitup in hours', 72)

    def _get_header(self, headers, name):
        for header in headers:
            if header[0] == name:
                return header[1]
        return None

    @match(r'^(get|head)\s+(\S+)$')
    def get(self, event, action, url):
        try:
            status, reason, data, headers = self._request(self._makeurl(url),
                                                          action.upper())
            reply = u'%s %s' % (status, reason)

            hops = 0
            while 300 <= status < 400 and self._get_header(headers, 'location'):
                location = self._get_header(headers, 'location')
                if not location:
                    break
                status, reason, data, headers = self._request(location, 'GET')
                if hops >= self.redirect_limit:
                    reply += u' to %s' % location
                    break
                hops += 1
                reply += u' to %(location)s, which gets a ' \
                         u'%(status)d %(reason)s' % {
                    u'location': location,
                    u'status': status,
                    u'reason': reason,
                }

            if action.upper() == 'GET':
                got_title = False
                content_type = self._get_header(headers, 'content-type')
                if content_type and (content_type.startswith('text/html') or
                        content_type.startswith('application/xhtml+xml')):
                    match = re.search(r'<title>(.*)<\/title>', data,
                                      re.I | re.DOTALL)
                    if match:
                        got_title = True
                        reply += u' "%s"' % match.groups()[0].strip()

                if not got_title and content_type:
                    reply += u' ' + content_type

            event.addresponse(reply)

        except HTTPException, e:
            event.addresponse(unicode(e))
Ejemplo n.º 8
0
class ICECast(Processor):
    usage = u"what's playing [on <stream>]?"
    feature = ('icecast', )

    interval = IntOption('interval',
                         'Interval between checking for song changes', 60)
    streams = DictOption(
        'streams',
        'Dictionary of Stream names to base URL (include trailing /)', {})

    last_checked = None
    last_songs = {}

    def scrape_status(self, stream):
        tree = get_html_parse_tree(self.streams[stream]['url'] + 'status.xsl',
                                   treetype='etree')
        main_table = tree.findall('.//table')[2]
        status = {}
        for row in main_table.findall('.//tr'):
            key, value = [x.text for x in row.findall('td')]
            status[key[:-1]] = value
        return status

    @match(r'^what(?:\'|\s+i)s\s+playing(?:\s+on\s+(.+))?$')
    def playing(self, event, stream):
        if not event.get('addressed', False):
            return

        if len(self.streams) == 0:
            event.addresponse(u"Sorry, I don't know about any streams")
            return
        elif stream is None and len(self.streams) == 1:
            stream = self.streams.keys()[0]
        elif stream is not None and stream not in self.streams:
            for name in self.streams.iterkeys():
                if name.lower() == stream.lower():
                    stream = name
                    break
            else:
                stream = None
        if stream is None:
            event.addresponse(
                u'Sorry, I only know about the following streams, '
                u'please choose one: %s', human_join(self.streams.keys()))
            return

        try:
            status = self.scrape_status(stream)
            event.addresponse(
                u'Currently Playing on %(stream)s: '
                u'%(song)s - %(description)s (Listeners: %(listeners)s)', {
                    'stream': stream,
                    'song': status['Current Song'],
                    'description': status['Stream Description'],
                    'listeners': status['Current Listeners'],
                })
        except HTTPError:
            event.addresponse(
                u'The stream must be down, back to the MP3 collection for you')

    @periodic(config_key='interval')
    def check(self, event):
        for name, stream in self.streams.iteritems():
            if 'source' in stream and 'channel' in stream:
                log.debug(u'Probing %s', name)
                status = self.scrape_status(name)
                if self.last_songs.get(name, '') != status['Current Song']:
                    self.last_songs[name] = status['Current Song']
                    event.addresponse(
                        u'Now Playing on %(stream)s: '
                        u'%(song)s - %(description)s '
                        u'(Listeners: %(listeners)s)',
                        {
                            'stream': name,
                            'song': status['Current Song'],
                            'description': status['Stream Description'],
                            'listeners': status['Current Listeners'],
                        },
                        source=stream['source'],
                        target=stream['channel'],
                        topic=(stream.get('topic', 'False').lower()
                               in ('yes', 'true')),
                        address=False,
                    )
Ejemplo n.º 9
0
class FlightSearch(Processor):
    usage = u"""airport [in] <name|location|code>
    [<cheapest|quickest>] flight from <departure> to <destination> from <depart_date> [anytime|morning|afternoon|evening|<time>] to <return_date> [anytime|morning|afternoon|evening|<time>]"""

    features = ('flight',)

    airports_url = u'http://openflights.svn.sourceforge.net/viewvc/openflights/openflights/data/airports.dat'
    max_results = IntOption('max_results', 'Maximum number of results to list', 5)

    airports = {}

    def read_airport_data(self):
        # File is listed as ISO 8859-1 (Latin-1) encoded on
        # http://openflights.org/data.html, but from decoding it appears to
        # actually be UTF8
        filename = cacheable_download(self.airports_url, u'flight/airports.dat')
        reader = csv.reader(open(filename), delimiter=',', quotechar='"')
        for row in reader:
            self.airports[int(row[0])] = [unicode(r, u'utf-8') for r in row[1:]]

    def _airport_search(self, query, search_loc = True):
        if not self.airports:
            self.read_airport_data()
        if search_loc:
            ids = self._airport_search(query, False)
            if len(ids) == 1:
                return ids
            query = [q for q in query.lower().split()]
        else:
            query = [query.lower()]
        ids = []
        for id, airport in self.airports.items():
            if search_loc:
                data = (u' '.join(c.lower() for c in airport[:5])).split()
            elif len(query[0]) == 3:
                data = [airport[3].lower()]
            else: # assume length 4 (won't break if not)
                data = [airport[4].lower()]
            if len(filter(lambda q: q in data, query)) == len(query):
                ids.append(id)
        return ids

    def repr_airport(self, id):
        airport = self.airports[id]
        code = u''
        if airport[3] or airport[4]:
            code = u' (%s)' % u'/'.join(filter(lambda c: c, airport[3:5]))
        return u'%s%s' % (airport[0], code)

    @match(r'^airports?\s+((?:in|for)\s+)?(.+)$')
    def airport_search(self, event, search_loc, query):
        search_loc = search_loc is not None
        if not search_loc and not 3 <= len(query) <= 4:
            event.addresponse(u'Airport code must be 3 or 4 characters')
            return
        ids = self._airport_search(query, search_loc)
        if len(ids) == 0:
            event.addresponse(u"Sorry, I don't know that airport")
        elif len(ids) == 1:
            id = ids[0]
            airport = self.airports[id]
            code = u'unknown code'
            if airport[3] and airport[4]:
                code = u'codes %s and %s' % (airport[3], airport[4])
            elif airport[3]:
                code = u'code %s' % airport[3]
            elif airport[4]:
                code = u'code %s' % airport[4]
            event.addresponse(u'%(airport)s in %(city)s, %(country)s has %(code)s', {
                u'airport': airport[0],
                u'city': airport[1],
                u'country': airport[2],
                u'code': code,
            })
        else:
            event.addresponse(u'Found the following airports: %s', human_join(self.repr_airport(id) for id in ids)[:480])

    def _flight_search(self, event, dpt, to, dep_date, ret_date):
        airport_dpt = self._airport_search(dpt)
        airport_to = self._airport_search(to)
        if len(airport_dpt) == 0:
            event.addresponse(u"Sorry, I don't know the airport you want to leave from")
            return
        if len(airport_to) == 0:
            event.addresponse(u"Sorry, I don't know the airport you want to fly to")
            return
        if len(airport_dpt) > 1:
            event.addresponse(u'The following airports match the departure: %s', human_join(self.repr_airport(id) for id in airport_dpt)[:480])
            return
        if len(airport_to) > 1:
            event.addresponse(u'The following airports match the destination: %s', human_join(self.repr_airport(id) for id in airport_to)[:480])
            return

        dpt = airport_dpt[0]
        to = airport_to[0]

        def to_travelocity_date(date):
            date = date.lower()
            time = None
            for period in [u'anytime', u'morning', u'afternoon', u'evening']:
                if period in date:
                    time = period.title()
                    date = date.replace(period, u'')
                    break
            try:
                date = parse(date)
            except ValueError:
                raise FlightException(u"Sorry, I can't understand the date %s" % date)
            if time is None:
                if date.hour == 0 and date.minute == 0:
                    time = u'Anytime'
                else:
                    time = date.strftime('%I:00')
                    if time[0] == u'0':
                        time = time[1:]
                    if date.hour < 12:
                        time += u'am'
                    else:
                        time += u'pm'
            date = date.strftime('%m/%d/%Y')
            return (date, time)

        (dep_date, dep_time) = to_travelocity_date(dep_date)
        (ret_date, ret_time) = to_travelocity_date(ret_date)

        params = {}
        params[u'leavingFrom'] = self.airports[dpt][3]
        params[u'goingTo'] = self.airports[to][3]
        params[u'leavingDate'] = dep_date
        params[u'dateLeavingTime'] = dep_time
        params[u'returningDate'] = ret_date
        params[u'dateReturningTime'] = ret_time
        etree = get_html_parse_tree('http://travel.travelocity.com/flights/InitialSearch.do', data=urlencode(params), treetype='etree')
        while True:
            script = [script for script in etree.getiterator(u'script')][1]
            matches = script.text and re.search(r'var finurl = "(.*)"', script.text)
            if matches:
                url = u'http://travel.travelocity.com/flights/%s' % matches.group(1)
                etree = get_html_parse_tree(url, treetype=u'etree')
            else:
                break

        # Handle error
        div = [d for d in etree.getiterator(u'div') if d.get(u'class') == u'e_content']
        if len(div):
            error = div[0].find(u'h3').text
            raise FlightException(error)

        departing_flights = self._parse_travelocity(etree)
        return_url = None
        table = [t for t in etree.getiterator(u'table') if t.get(u'id') == u'tfGrid'][0]
        for tr in table.getiterator(u'tr'):
            for td in tr.getiterator(u'td'):
                if td.get(u'class').strip() in [u'tfPrice', u'tfPriceOrButton']:
                    onclick = td.find(u'div/button').get(u'onclick')
                    match = re.search(r"location.href='\.\./flights/(.+)'", onclick)
                    url_page = match.group(1)
                    match = re.search(r'^(.*?)[^/]*$', url)
                    url_base = match.group(1)
                    return_url = url_base + url_page

        etree = get_html_parse_tree(return_url, treetype=u'etree')
        returning_flights = self._parse_travelocity(etree)

        return (departing_flights, returning_flights, url)

    def _parse_travelocity(self, etree):
        flights = []
        table = [t for t in etree.getiterator(u'table') if t.get(u'id') == u'tfGrid'][0]
        trs = [t for t in table.getiterator(u'tr')]
        tr_index = 1
        while tr_index < len(trs):
            tds = []
            while True:
                new_tds = [t for t in trs[tr_index].getiterator(u'td')]
                tds.extend(new_tds)
                tr_index += 1
                if len(filter(lambda t: t.get(u'class').strip() == u'tfAirlineSeatsMR', new_tds)):
                    break
            flight = Flight()
            for td in tds:
                if td.get(u'class').strip() == u'tfAirline':
                    anchor = td.find(u'a')
                    if anchor is not None:
                        airline = anchor.text.strip()
                    else:
                        airline = td.text.split(u'\n')[0].strip()
                    flight.flight.append(u'%s %s' % (airline, td.findtext(u'div').strip()))
                if td.get(u'class').strip() == u'tfDepart' and td.text:
                    flight.depart_time = td.text.split(u'\n')[0].strip()
                    flight.depart_ap = u'%s %s' % (td.findtext(u'div').strip(),
                            td.findtext(u'div/span').strip())
                if td.get(u'class').strip() == u'tfArrive' and td.text:
                    flight.arrive_time = td.text.split(u'\n')[0].strip()
                    span = td.find(u'span')
                    if span is not None and span.get(u'class').strip() == u'tfNextDayDate':
                        flight.arrive_time = u'%s %s' % (flight.arrive_time, span.text.strip()[2:])
                        span = [s for s in td.find(u'div').getiterator(u'span')][1]
                        flight.arrive_ap = u'%s %s' % (td.findtext(u'div').strip(),
                                span.text.strip())
                    else:
                        flight.arrive_ap = u'%s %s' % (td.findtext(u'div').strip(),
                                td.findtext(u'div/span').strip())
                if td.get(u'class').strip() == u'tfTime' and td.text:
                    flight.duration = td.text.strip()
                    flight.stops = td.findtext(u'span/a').strip()
                if td.get(u'class').strip() in [u'tfPrice', u'tfPriceOr'] and td.text:
                    flight.price = td.text.strip()
            flight.flight = human_join(flight.flight)
            flights.append(flight)

        return flights

    @match(r'^(?:(cheapest|quickest)\s+)?flights?\s+from\s+(.+)\s+to\s+(.+)\s+from\s+(%s)\s+to\s+(%s)$' % (DATE, DATE), simple=False)
    def flight_search(self, event, priority, dpt, to, dep_date, ret_date):
        try:
            flights = self._flight_search(event, dpt, to, dep_date, ret_date)
        except FlightException, e:
            event.addresponse(unicode(e))
            return
        if flights is None:
            return
        if len(flights[0]) == 0:
            event.addresponse(u'No matching departure flights found')
            return
        if len(flights[1]) == 0:
            event.addresponse(u'No matching return flights found')
            return

        cmp = None
        if priority is not None:
            priority = priority.lower()
        if priority == u'cheapest':
            cmp = lambda a, b: a.int_price() < b.int_price()
        elif priority == u'quickest':
            cmp = lambda a, b: a.int_duration() < b.int_duration()
        if cmp:
            # select best flight based on priority
            for i in xrange(2):
                flights[i].sort(cmp=cmp)
                del flights[i][1:]
        response = []
        for i, flight_type in zip(xrange(2), [u'Departing', u'Returning']):
            if len(flights[i]) > 1:
                response.append(u'%s flights:' % flight_type)
            for flight in flights[i][:self.max_results]:
                leading = u''
                if len(flights[i]) == 1:
                    leading = u'%s flight: ' % flight_type
                response.append(u'%(leading)s%(flight)s departing %(depart_time)s from %(depart_airport)s, arriving %(arrive_time)s at %(arrive_airport)s (flight time %(duration)s, %(stops)s) costs %(price)s per person' % {
                    'leading': leading,
                    'flight': flight.flight,
                    'depart_time': flight.depart_time,
                    'depart_airport': flight.depart_ap,
                    'arrive_time': flight.arrive_time,
                    'arrive_airport': flight.arrive_ap,
                    'duration': flight.duration,
                    'stops': flight.stops,
                    'price': flight.price or 'unknown'
                })
        response.append(u'Full results: %s' % flights[2])
        event.addresponse(u'\n'.join(response), conflate=False)
Ejemplo n.º 10
0
class Poll(Processor):
    usage = u"""
    [secret] poll on <topic> [until <time>] vote <option> [or <option>]...
    vote (<id> | <option>) [on <topic>]
    end poll
    """
    features = ('poll', )
    permission = u'chairmeeting'

    polls = {}

    date_utc = BoolOption('date_utc', u'Interpret poll end times as UTC',
                          False)
    poll_time = IntOption('poll_time', u'Default poll length', 5 * 60)

    @authorise(fallthrough=False)
    @match(r'^(secret\s+)?(?:poll|ballot)\s+on\s+(.+?)\s+'
           r'(?:until\s+(.+?)\s+)?vote\s+(.+\sor\s.+)$')
    def start_poll(self, event, secret, topic, end, options):
        if not event.public:
            event.addresponse(u'Sorry, must be done in public')
            return

        if (event.source, event.channel) in self.polls:
            event.addresponse(u'Sorry, poll on %s in progress.',
                              self.polls[(event.source, event.channel)].topic)
            return

        class PollContainer(object):
            pass

        poll = PollContainer()
        self.polls[(event.source, event.channel)] = poll

        poll.secret = secret is not None
        if end is None:
            poll.end = event.time + timedelta(seconds=self.poll_time)
        else:
            poll.end = parse(end)
            if poll.end.tzinfo is None and not self.date_utc:
                poll.end = poll.end.replace(tzinfo=tzlocal())
            if poll.end.tzinfo is not None:
                poll.end = poll.end.astimezone(tzutc()).replace(tzinfo=None)
        if poll.end < event.time:
            event.addresponse(u"I can't end a poll in the past")
            return

        poll.topic = topic
        poll.options = re.split(r'\s+or\s+', options)
        poll.lower_options = [o.lower() for o in poll.options]
        poll.votes = {}

        event.addresponse(
            u'You heard that, voting has begun. '
            u'The polls close at %(end)s. '
            u'%(private)s'
            u'The Options Are:', {
                'private':
                poll.secret and u'You may vote in public or private. ' or u'',
                'end':
                format_date(poll.end),
            },
            address=False)

        for i, o in enumerate(poll.options):
            event.addresponse(u'%(id)i: %(option)s', {
                'id': i + 1,
                'option': o,
            },
                              address=False)

        delay = poll.end - event.time
        poll.delayed_call = ibid.dispatcher.call_later(
            delay.days * 86400 + delay.seconds, self.end_poll, event)

    def locate_poll(self, event, selection, topic):
        "Attempt to find which poll the user is voting in"
        if event.public:
            if (event.source, event.channel) in self.polls:
                return self.polls[(event.source, event.channel)]
        else:
            if topic:
                polls = [
                    p for p in self.polls.iteritems()
                    if p.topic.lower() == topic.lower()
                ]
                if len(polls) == 1:
                    return polls[0]
            polls = [
                self.polls[p] for p in self.polls.iterkeys()
                if p[0] == event.source
            ]
            if len(polls) == 1:
                return polls[0]
            elif len(polls) > 1:
                if not selection.isdigit():
                    possibles = [
                        p for p in polls
                        if selection.lower() in p.lower_options
                    ]
                    if len(possibles) == 1:
                        return possibles[0]
                event.addresponse(
                    u'Sorry, I have more than one poll open. '
                    u'Please say "vote %s on <topic>"', selection)
                return
        event.addresponse(u'Sorry, no poll in progress')

    @match(r'^vote\s+(?:for\s+)?(.+?)(?:\s+on\s+(.+))?$')
    def vote(self, event, selection, topic):
        poll = self.locate_poll(event, selection, topic)
        log.debug(u'Poll: %s', repr(poll))
        if poll is None:
            return

        if selection.isdigit() and int(selection) > 0 \
                and int(selection) <= len(poll.options):
            selection = int(selection) - 1
        else:
            try:
                selection = poll.lower_options.index(selection)
            except ValueError:
                event.addresponse(
                    u"Sorry, I don't know of such an option for %s",
                    poll.topic)
                return
        poll.votes[event.identity] = selection
        if not event.public:
            event.addresponse(
                u'Your vote on %(topic)s has been registered as %(option)s', {
                    'topic': poll.topic,
                    'option': poll.options[selection],
                })
        else:
            event.processed = True

    @match(r'^end\s+poll$')
    @authorise()
    def end_poll(self, event):
        if not event.public:
            event.addresponse(u'Sorry, must be done in public')
            return

        if (event.source, event.channel) not in self.polls:
            event.addresponse(u'Sorry, no poll in progress.')
            return

        poll = self.polls.pop((event.source, event.channel))
        if poll.delayed_call.active():
            poll.delayed_call.cancel()

        votes = [[poll.options[i], 0] for i in range(len(poll.options))]
        for vote in poll.votes.itervalues():
            votes[vote][1] += 1
        votes.sort(reverse=True, key=lambda x: x[1])
        event.addresponse(u'The polls are closed. Totals:', address=False)

        position = (1, votes[0][1])
        for o, v in votes:
            if v < position[1]:
                position = (position[0] + 1, v)
            event.addresponse(u'%(position)i: %(option)s - %(votes)i %(word)s',
                              {
                                  'position': position[0],
                                  'option': o,
                                  'votes': v,
                                  'word': plural(v, u'vote', u'votes'),
                              },
                              address=False)
Ejemplo n.º 11
0
Archivo: ascii.py Proyecto: 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)
Ejemplo n.º 12
0
class WerewolfGame(Processor):
    usage = u"""
    start a game of werewolf
    join
    ( kill | see | eat ) <villager>
    vote for <villager>
    """
    feature = ('werewolf', )
    state = None

    player_limit = IntOption('min_players', 'The minimum number of players', 5)
    start_delay = IntOption('start_delay',
                            'How long to wait before starting, in seconds', 60)
    day_length = IntOption('day_length', 'Length of day / night, in seconds',
                           60)
    addressed = BoolOption('addressed', 'Messages must be addressed to bot',
                           True)
    players_per_wolf = IntOption('players_per_wolf',
                                 'Number of players to each wolf/seer', 4)
    seer_delay = IntOption(
        'seer_delay', 'Number of players between extra wolf and extra seer', 4)

    event_types = (u'message', u'action')

    @match(r'^(?:start|play|begin)s?\b.*werewolf$')
    def prestart(self, event):
        """Initiate a game.

        This is the state from initiation to start of game.
        Next state is start.
        """
        if self.state:
            log.debug(u'Not starting game: already in state %s.',
                      self.state_name())
            return

        if not event.public:
            log.debug(u'Event is not public.')
            event.addresponse(u'You must start the game in public.')
            return

        self.state = self.prestart
        self.channel = event.channel

        log.debug(u'Starting game.')

        werewolf_games.append(self)

        starter = event.sender['nick']
        self.players = set((starter, ))
        event.addresponse(
            u'You have started a game of Werewolf. '
            u'Everybody has %i seconds to join the game.', self.start_delay)

        self.timed_goto(event, self.start_delay, self.start)

    @match(r'^joins?\b')
    def join(self, event):
        if self.state != self.prestart:
            log.debug(u'Not joining: already in state %s.', self.state_name())
            return

        if event.sender['nick'] not in self.players:
            self.players.add(event.sender['nick'])
            event.addresponse(u'%(player)s has joined (%(num)i players).', {
                'num': len(self.players),
                'player': event.sender['nick']
            },
                              target=self.channel,
                              address=False)
        else:
            event.addresponse(u'You have already joined the game.')

    def start(self, event):
        """Start game.

        Players are assigned their roles. The next state is night.
        """
        self.state = self.start

        if len(self.players) < self.player_limit:
            event.addresponse(u'Not enough players. Try again later.')
            self.state = None
            return

        event.addresponse(
            u'%i players joined. Please wait while I assign roles.',
            len(self.players))

        self.players = list(self.players)
        shuffle(self.players)

        nwolves = max(1, len(self.players) // self.players_per_wolf)
        nseers = max(1, (len(self.players) - self.seer_delay) //
                     self.players_per_wolf)
        self.wolves = set(self.players[:nwolves])
        self.seers = set(self.players[nwolves:nwolves + nseers])

        self.roles = dict((player, 'villager') for player in self.players)
        del self.players

        for player in self.wolves:
            self.roles[player] = 'wolf'

        for player in self.seers:
            self.roles[player] = 'seer'

        for player, role in self.roles.iteritems():
            event.addresponse(u'%(name)s, you are a %(role)s.', {
                'name': player,
                'role': role,
            },
                              target=player,
                              address=False)

        if nwolves > 1 and nseers > 1:
            event.addresponse(
                u'This game has %(seers)i seers and %(wolves)i wolves.', {
                    'seers': nseers,
                    'wolves': nwolves,
                })
        elif nwolves > 1:
            event.addresponse(u'This game has %i wolves.', nwolves)
        elif nseers > 1:
            event.addresponse(u'This game has %i seers.', nseers)

        self.timed_goto(event, 10, self.night)

    def night(self, event):
        """Start of night.

        Tell seer and werewolf to act.

        This state lasts for the whole night. The next state is dawn.
        """
        self.state = self.night
        event.addresponse(
            u'Night falls... most villagers are sleeping, '
            u'but outside, something stirs.\n' +
            plural(len(self.wolves), u'Werewolf, you may kill somebody.',
                   u'Werewolves, you may kill somebody.') + '\n' +
            plural(len(
                self.seers), u"Seer, you may discover somebody's true form.",
                   u"Seers, you may discover somebody's true form."),
            conflate=False)
        self.say_survivors(event)

        self.wolf_targets = {}
        self.seer_targets = {}

        self.timed_goto(event, self.day_length, self.dawn)

    @match(r'^(?:kill|see|eat)s?\s+(\S+)$')
    def kill_see(self, event, target_nick):
        """Kill or see a player.

        Only works for seers and wolves.
        """
        if (self.state != self.night or event.public
                or event.sender['nick'] not in self.roles):
            return

        sender = event.sender['nick']
        target = self.identify(target_nick)
        if target is None:
            event.addresponse(u'%s is not playing.', target_nick)
        elif self.roles[sender] == 'wolf':
            event.addresponse(u'You have chosen %s for your feast tonight.',
                              target_nick)
            self.wolf_targets[sender] = target
        elif self.roles[sender] == 'seer':
            event.addresponse(u"You will discover %s's role at dawn tomorrow.",
                              target_nick)
            self.seer_targets[sender] = target

    def dawn(self, event):
        """Start of day.

        During this state, villagers discover what happened overnight and
        discuss who to lynch. The next state is noon.
        """
        self.state = self.dawn

        eaten = frozenset(self.wolf_targets.itervalues())
        if eaten:
            victim = choice(list(eaten))
            event.addresponse(
                u'The village awakes to find that werewolves have '
                u'devoured %(nick)s the %(role)s in the night.', {
                    'nick': victim,
                    'role': self.roles[victim],
                })
            self.death(victim)
        else:
            event.addresponse(u'The werewolves were abroad last night.')
        self.wolf_targets = {}

        for seer in self.seers:
            target = self.seer_targets.get(seer)
            if target is not None:
                # seer saw somebody
                if target in self.roles:
                    # that somebody is alive
                    msg = u'%(nick)s is a %(role)s' % {
                        'nick': target,
                        'role': self.roles[target],
                    }
                else:
                    msg = u'The wolves also had %s in mind last night.' \
                        % target

                event.addresponse(msg, target=seer)
        self.seer_targets = {}

        if not self.endgame(event):
            event.addresponse(
                u'Villagers, you have %i seconds '
                u'to discuss suspicions and cast accusations.',
                self.day_length)
            self.say_survivors(event)

            self.timed_goto(event, self.day_length, self.noon)

    def noon(self, event):
        """Start of voting.

        Next state is dusk.
        """
        self.state = self.noon
        event.addresponse(
            u'Villagers, you have %i seconds to cast '
            u'your vote to lynch somebody.', self.day_length)

        self.votes = {}

        self.timed_goto(event, self.day_length, self.dusk)

    @match(r'^(?:lynch(?:es)?|votes?)\s+(?:for|against)\s+(\S+)$')
    def vote(self, event, target_nick):
        """Vote to lynch a player."""

        if (self.state != self.noon or event.sender['nick'] not in self.roles):
            return

        target = self.identify(target_nick)
        if target is None:
            event.addresponse(u'%s is not playing.', target_nick)
        else:
            self.votes[event.sender['nick']] = target
            event.addresponse(u'%(voter)s voted for %(target)s.', {
                'target': target,
                'voter': event.sender['nick'],
            },
                              target=self.channel,
                              address=False)

    def dusk(self, event):
        """Counting of votes and lynching.

        Next state is night.
        """
        self.state = self.dusk
        vote_counts = defaultdict(int)
        for vote in self.votes.values():
            vote_counts[vote] += 1
        self.votes = {}

        victims = []
        victim_votes = 0
        for player, votes in vote_counts.iteritems():
            if votes > victim_votes:
                victims = [player]
                victim_votes = votes
            elif votes == victim_votes:
                victims.append(player)

        if victims:
            if len(victims) > 1:
                event.addresponse(u'The votes are tied. Picking randomly...')
            victim = choice(victims)
            event.addresponse(
                u'The ballots are in, '
                u'and %(nick)s the %(role)s has been lynched.', {
                    'nick': victim,
                    'role': self.roles[victim],
                })
            self.death(victim)
        else:
            event.addresponse(u'Nobody voted.')

        if not self.endgame(event):
            self.timed_goto(event, 10, self.night)

    def say_survivors(self, event):
        """Name surviving players."""

        event.addresponse(u'The surviving villagers are: %s.',
                          human_join(self.roles))

    def identify(self, nick):
        """Find the identity (correctly-capitalised nick) of a player.

        Returns None if nick is not playing.
        """
        for player in self.roles.iterkeys():
            if player.lower() == nick.lower():
                return player
        return None

    def death(self, player):
        """Remove player from game."""

        if self.state == self.prestart:
            self.players.remove(player)
        elif self.state is not None:
            del self.roles[player]

            for role in (self.wolves, self.seers):
                try:
                    role.remove(player)
                except KeyError:
                    pass

    def endgame(self, event):
        """Check if the game is over.

        If the game is over, announce the winners and return True. Otherwise
        return False.
        """

        if 2 * len(self.wolves) >= len(self.roles):
            # werewolves win
            event.addresponse(
                u'The werewolves devour the remaining '
                u'villagers and win. OM NOM NOM.\n'
                u'The winning werewolves were: %s',
                human_join(self.wolves),
                conflate=False)
        elif not self.wolves:
            # villagers win
            event.addresponse(
                u'The villagers have defeated the werewolves. '
                u'Vigilantism FTW.\n'
                u'The surviving villagers were: %s',
                human_join(self.roles),
                conflate=False)
        else:
            return False

        self.state = None
        werewolf_games.remove(self)
        return True

    def timed_goto(self, event, delay, target):
        """Like call_later, but does nothing if state has changed."""

        from_state = self.state
        log.debug(u'Going from state %s to %s in %i seconds.',
                  self.state_name(), target.__name__, delay)

        def goto(evt):
            """Change state if it hasn't already changed."""
            if self.state == from_state:
                target(evt)

        ibid.dispatcher.call_later(delay, goto, event)

    def rename(self, oldnick, newnick):
        """Rename a player."""

        for playerset in ('players', 'wolves', 'seers'):
            if hasattr(self, playerset):
                playerset = getattr(self, playerset)
                if oldnick in playerset:
                    playerset.remove(oldnick)
                    playerset.add(newnick)

        if hasattr(self, 'roles') and oldnick in self.roles:
            self.roles[newnick] = self.roles[oldnick]
            del self.roles[oldnick]

    def state_change(self, event):
        if self.state is None:
            return

        if not hasattr(event, 'state'):
            return

        if event.state != 'online':
            nick = event.sender['nick']
            if hasattr(event, 'othername'):
                self.rename(event.othername, nick)
            elif ((self.state == self.prestart and nick in self.players)
                  or nick in self.roles):
                event.addresponse(u'%s has fled the game in terror.',
                                  nick,
                                  target=self.channel,
                                  address=False)
                self.death(nick)

    def state_name(self):
        "Return a printable version of the current state"
        if self.state is None:
            return 'stopped'
        return self.state.__name__
Ejemplo n.º 13
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)
Ejemplo n.º 14
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)
Ejemplo n.º 15
0
Archivo: log.py Proyecto: 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:
Ejemplo n.º 16
0
                                                 name)
                            method.im_func.failing = True
                        else:
                            self.__log.debug(u'Still failing: %s', name)

            finally:
                method.lock.release()

# This is a bit yucky, but necessary since ibid.config imports Processor
from ibid.config import BoolOption, IntOption
options = {
    'addressed': BoolOption('addressed',
        u'Only process events if bot was addressed'),
    'processed': BoolOption('processed',
        u"Process events even if they've already been processed"),
    'priority': IntOption('priority', u'Processor priority'),
}

def handler(function):
    "Wrapper: Handle all events"
    function.handler = True
    function.message_version = 'clean'
    return function

def _match_sub_selectors(regex):
    selector_patterns = {
        'alpha'   : r'[a-zA-Z]+',
        'any'     : r'.+',
        'chunk'   : r'\S+',
        'digits'  : r'\d+',
        'number'  : r'\d*\.?\d+',
Ejemplo n.º 17
0
Archivo: irc.py Proyecto: 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
Ejemplo n.º 18
0
class Search(Processor):
    usage = u'search [for] [<limit>] [(facts|values) [containing]] (<pattern>|/<pattern>/[r]) [from <start>]'
    features = ('factoid', )

    limit = IntOption('search_limit', u'Maximum number of results to return',
                      30)
    default = IntOption('search_default',
                        u'Default number of results to return', 10)

    regex_re = re.compile(r'^/(.*)/(r?)$')

    @match(
        r'^search\s+(?:for\s+)?(?:(\d+)\s+)?(?:(facts?|values?)\s+)?(?:containing\s+)?(.+?)(?:\s+from)?(?:\s+(\d+))?\s*$',
        version='deaddressed')
    def search(self, event, limit, search_type, pattern, start):
        limit = limit and min(int(limit), self.limit) or self.default
        start = start and max(int(start) - 1, 0) or 0

        search_type = search_type and search_type.lower() or u""

        origpattern = pattern
        m = self.regex_re.match(pattern)
        is_regex = False
        if m:
            pattern = m.group(1)
            is_regex = bool(m.group(2))

        # Hack: We replace $arg with _%, but this won't match a partial
        # "$arg" string
        if is_regex:
            filter_op = get_regexp_op(event.session)
            name_pattern = pattern.replace(r'\$arg', '_%')
        else:
            filter_op = lambda x, y: x.like(y, escape='#')
            pattern = '%%%s%%' % escape_like_re.sub(r'#\1', pattern)
            name_pattern = pattern.replace('$arg', '#_#%')

        query = event.session.query(Factoid)\
                             .join(Factoid.names).add_entity(FactoidName)\
                             .join(Factoid.values)

        if search_type.startswith('fact'):
            query = query.filter(filter_op(FactoidName.name, name_pattern))
        elif search_type.startswith('value'):
            query = query.filter(filter_op(FactoidValue.value, pattern))
        else:
            query = query.filter(
                or_(filter_op(FactoidName.name, name_pattern),
                    filter_op(FactoidValue.value, pattern)))

        # Pre-evalute the iterable or the if statement will be True in SQLAlchemy 0.4. LP: #383286
        matches = [match for match in query.all()]

        bounded_matches = matches[start:start + limit]
        if bounded_matches:
            event.addresponse(u'; '.join(
                u'%s [%s]' % (fname.name, len(factoid.values))
                for factoid, fname in bounded_matches))
        elif len(matches):
            event.addresponse(
                u"I could only find %(number)d things that matched '%(pattern)s'",
                {
                    u'number': len(matches),
                    u'pattern': origpattern,
                })
        else:
            event.addresponse(u"I couldn't find anything that matched '%s'" %
                              origpattern)
Ejemplo n.º 19
0
class MemoryLog(Processor):

    feature = ('memory', )
    autoload = False

    mem_filename = Option('mem_filename', 'Memory log filename',
                          'logs/memory.log')
    mem_interval = IntOption('mem_interval',
                             'Interval between memory stat logging', 0)
    obj_filename = Option('obj_filename', 'Object Statistics log filename',
                          'logs/objstats.log')
    obj_interval = IntOption('obj_interval',
                             'Interval between logging object statistics', 0)

    def setup(self):
        fns = []
        if self.mem_interval:
            fns.append(self.mem_filename)
        if self.obj_interval:
            fns.append(self.obj_filename)
        for filename in fns:
            if os.path.isfile(filename + '.10.gz'):
                os.remove(filename + '.10.gz')
            for i in range(9, 0, -1):
                if os.path.isfile('%s.%i.gz' % (filename, i)):
                    os.rename('%s.%i.gz' % (filename, i),
                              '%s.%i.gz' % (filename, i + 1))
            if os.path.isfile(filename):
                o = gzip.open(filename + '.1.gz', 'wb')
                i = open(filename, 'rb')
                o.write(i.read())
                o.close()
                i.close()
                stat = os.stat(filename)
                os.utime(filename + '.1.gz', (stat.st_atime, stat.st_mtime))

        if self.mem_interval:
            self.mem_file = file(self.mem_filename, 'w+')
            self.mem_file.write('Ibid Memory Log v2: %s\n' %
                                ibid.config['botname'])
            self.mem_csv = csv.writer(self.mem_file)
            self.mem_last = datetime.utcnow()

        if self.obj_interval:
            self.obj_file = file(self.obj_filename, 'w+')
            self.obj_file.write('Ibid Object Log v1: %s\n' %
                                ibid.config['botname'])
            self.obj_last = datetime.utcnow()

    def process(self, event):
        if self.mem_interval and event.time - self.mem_last >= \
                timedelta(seconds=self.mem_interval):
            self.mem_log()
            self.mem_last = event.time
        if self.obj_interval and event.time - self.obj_last >= \
                timedelta(seconds=self.obj_interval):
            self.obj_log()
            self.obj_last = event.time

    def mem_log(self):
        status = get_memusage()
        gc.collect()

        self.mem_csv.writerow((
            datetime.utcnow().isoformat(),
            len(gc.get_objects()),
            status['VmSize'],
            status['VmRSS'],
        ))
        self.mem_file.flush()

    def obj_log(self):
        self.obj_file.write(
            '%s %s\n' %
            (datetime.utcnow().isoformat(), json.dumps(objgraph.typestats())))
        self.obj_file.flush()
Ejemplo n.º 20
0
Archivo: fun.py Proyecto: 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)
Ejemplo n.º 21
0
Archivo: feeds.py Proyecto: vhata/ibid
class Retrieve(Processor):
    usage = u"""latest [ <count> ] ( articles | headlines ) from <name> [ starting at <number> ]
    article ( <number> | /<pattern>/ ) from <name>"""
    features = ('feeds',)

    interval = IntOption('interval', 'Feed Poll interval (in seconds)', 300)
    max_interval = IntOption('max_interval',
        'Maximum feed poll interval for broken feeds (in seconds)', 86400)
    backoff_ratio = FloatOption('backoff',
        'The slowdown ratio to back off from broken feeds', 2.0)


    @match(r'^(?:latest|last)\s+(?:(\d+)\s+)?(article|headline)(s)?\s+from\s+(.+?)'
           r'(?:\s+start(?:ing)?\s+(?:at\s+|from\s+)?(\d+))?$')
    def list(self, event, number, full, plurality, name, start):
        full = full == 'article'
        if number:
            number = int(number)
        elif not plurality:
            number = 1
        else:
            number = 10
        start = start and int(start) or 0

        feed = event.session.query(Feed).filter_by(name=name).first()

        if not feed:
            event.addresponse(u"I don't know about the %s feed", name)
            return

        feed.update()
        if not feed.entries:
            event.addresponse(u"I can't find any articles in that feed")
            return

        articles = feed.entries[start:number+start]
        entries = []
        for article in articles:
            if full:
                if 'summary' in article:
                    summary = html2text_file(article.summary, None)
                else:
                    if article.content[0].type in \
                            ('application/xhtml+xml', 'text/html'):
                        summary = html2text_file(article.content[0].value, None)
                    else:
                        summary = article.content[0].value

                entries.append(u'%(number)s: "%(title)s"%(link)s : %(summary)s' % {
                    'number': articles.index(article) + 1,
                    'title': html2text_file(article.title, None).strip(),
                    'link': get_link(article),
                    'summary': summary,
                })
            else:
                entries.append(u'%s: "%s"' % (feed.entries.index(article) + 1, html2text_file(article.title, None).strip()))
        event.addresponse(u', '.join(entries))

    @match(r'^article\s+(?:(\d+)|/(.+?)/)\s+from\s+(.+?)$')
    def article(self, event, number, pattern, name):
        feed = event.session.query(Feed).filter_by(name=name).first()

        if not feed:
            event.addresponse(u"I don't know about the %s feed", name)
            return

        feed.update()
        if not feed.entries:
            event.addresponse(u"I can't find any articles in that feed")
            return
        article = None

        if number:
            if int(number) > len(feed.entries) or 1 > int(number):
                event.addresponse(u"That's old news dude")
                return
            article = feed.entries[int(number) - 1]

        else:
            pattern = re.compile(pattern, re.I)
            for entry in feed.entries:
                if pattern.search(entry.title):
                    article = entry
                    break

            if not article:
                event.addresponse(u'Are you making up news again?')
                return

        if 'summary' in article:
            summary = html2text_file(article.summary, None)
        else:
            if article.content[0].type in \
                    ('application/xhtml+xml', 'text/html'):
                summary = html2text_file(article.content[0].value, None)
            else:
                summary = article.content[0].value

        event.addresponse(u'"%(title)s"%(link)s : %(summary)s', {
            'title': html2text_file(article.title, None).strip(),
            'link': get_link(article),
            'summary': summary,
        })

    last_seen = {}
    @periodic(config_key='interval')
    def poll(self, event):
        feeds = event.session.query(Feed) \
                .filter(Feed.source != None) \
                .filter(Feed.target != None).all()

        for feed in feeds:
            broken_lock.acquire()
            try:
                if feed.name in broken_feeds:
                    last_exc, interval, time_since_fetch = broken_feeds[feed.name]
                    time_since_fetch += self.interval
                    if time_since_fetch < interval:
                        broken_feeds[feed.name] = \
                                last_exc, interval, time_since_fetch
                        continue
                else:
                    last_exc = None
                    interval = time_since_fetch = self.interval

                try:
                    feed.update(max_age=time_since_fetch)
                except Exception, e:
                    if type(e) != type(last_exc):
                        if isinstance(e, URLError):
                            log.warning(u'Exception "%s" occured while polling '
                                        u'feed %s from %s', e, feed, feed.url)
                        else:
                            log.exception(u'Exception "%s" occured while polling '
                                          u'feed %s from %s', e, feed, feed.url)
                    broken_feeds[feed.name] = e, self.backoff(interval), 0
                    continue
                else:
                    if feed.name in broken_feeds:
                        del broken_feeds[feed.name]
            finally:
Ejemplo n.º 22
0
Archivo: fun.py Proyecto: vhata/ibid
class ExchangeMessage(Processor):
    usage = u"""(have|take) <object>
    what are you carrying?
    who gave you <object>?
    give <person> <object>"""
    features = ('bucket', )

    bucket_size = IntOption('bucket_size',
                            "The maximum number of objects in the bucket", 5)

    @match(r'(?:have|take) ' + object_pat)
    def have(self, event, determiner, object):
        if determiner in ('his', 'her', 'their', 'its'):
            event.addresponse("I don't know whose %s you're talking about",
                              object)
        else:
            return exchange(event, determiner, object, self.bucket_size)

    @match(r'(?:what (?:are|do) (?:yo)?u )?(?:carrying|have)')
    def query_carrying(self, event):
        items = Item.carried_items(event.session).all()
        if items:
            event.addresponse(u"I'm carrying %s",
                              human_join(map(unicode, items)))
        else:
            event.addresponse(u"I'm not carrying anything")

    def find_items(self, session, determiner, object):
        """Find items matching (determiner, object).

        Return a tuple (kind, items).

        If determiner is a genitive and there are matching objects with the
        correct owner, return them with kind='owned'; if there are no matching
        objects, find unowned objects and return with kind='unowned'. If the
        determiner is *not* genitive, ignore determiners and set kind='all'."""

        all_items = Item.carried_items(session) \
                    .filter_by(description=object)

        if "'" in determiner:
            items = all_items.filter_by(determiner=determiner).all()
            if items:
                return ('owned', items)
            else:
                # find unowned items
                clause = or_(not_(Item.determiner.contains(u"'")),
                             Item.determiner == None)
                return ('unowned', all_items.filter(clause).all())
        else:
            return ('all', all_items.all())

    @match(r'give {chunk} ' + object_pat)
    def give(self, event, receiver, determiner, object):
        if determiner is None:
            determiner = ''

        who = event.sender['nick']
        if determiner.lower() == 'our':
            if who[-1] in 'sS':
                determiner = who + "'"
            else:
                determiner = who + "'s"
            yours = 'your'
        elif determiner.lower() == 'my':
            determiner = who + "'s"
            yours = 'your'
        elif determiner.lower() in ('his', 'her', 'their'):
            yours = determiner.lower()
            determiner = receiver + "'s"
        else:
            yours = False

        if receiver.lower() == 'me':
            receiver = who

        kind, items = self.find_items(event.session, determiner, object)

        if items:
            item = choice(items)
            item.carried = False
            event.session.save_or_update(item)

            if kind == 'owned' and yours and yours != 'your':
                item.determiner = yours
            event.addresponse(u'hands %(receiver)s %(item)s ', {
                'receiver': receiver,
                'item': item
            },
                              action=True)
        else:
            if yours:
                object = yours + u' ' + object
            elif determiner:
                object = determiner + u' ' + object
            event.addresponse(
                choice((u"There's nothing like that in my bucket.",
                        u"I don't have %s" % object)))

    @match(r'(?:who gave (?:yo)?u|where did (?:yo)?u get) ' + object_pat)
    def query_giver(self, event, determiner, object):
        if determiner is None:
            determiner = ''

        who = event.sender['nick']
        if determiner.lower() == 'our':
            if who[-1] in 'sS':
                determiner = who + "'"
            else:
                determiner = who + "'s"
            yours = True
        elif determiner.lower() == 'my':
            determiner = who + "'s"
            yours = True
        else:
            yours = False

        kind, items = self.find_items(event.session, determiner, object)

        if items:
            explanation = u''
            if kind == 'unowned':
                explanation = plural(len(items), u". I didn't realise it was ",
                                     u". I didn't realise they were ")
                if yours:
                    explanation += u"yours"
                else:
                    explanation += determiner
                explanation += u"."
                yours = False

            event.addresponse(u'I got ' + human_join(
                u'%(item)s from %(giver)s' % {
                    'item': [item, u'your ' + item.description][yours],
                    'giver': identity_name(event, item.giver)
                } for item in items) + explanation)
            return
        else:
            if yours:
                object = u'your ' + object
            elif determiner:
                object = determiner + u' ' + object
            event.addresponse(
                choice((u"There's nothing like that in my bucket.",
                        u"I don't have %s" % object)))
Ejemplo n.º 23
0
class Dict(Processor):
    usage = u"""spell <word> [using <strategy>]
    define <word> [using <dictionary>]
    (dictionaries|strategies)
    (dictionary|strategy) <name>"""
    feature = ('dict',)

    server = Option('server', 'Dictionary server hostname', 'localhost')
    port = IntOption('port', 'Dictionary server port number', 2628)

    @staticmethod
    def reduce_suggestions(suggestions):
        "Remove duplicate suggestions and suffixes"
        output = []
        for s in suggestions:
            s = s.getword()
            if not s.startswith('-') and s not in output:
                output.append(s)
        return output

    @match(r'^(?:define|dict)\s+(.+?)(?:\s+using\s+(.+))?$')
    def define(self, event, word, dictionary):
        connection = Connection(self.server, self.port)
        dictionary = dictionary is None and '*' or dictionary.lower()
        dictionaries = connection.getdbdescs().keys()

        if dictionary != '*' and dictionary not in dictionaries:
            event.addresponse(
                    u"I'm afraid I don't have a dictionary of that name. I know about: %s",
                    human_join(sorted(dictionaries)))
            return

        definitions = connection.define(dictionary, word.encode('utf-8'))
        if definitions:
            event.addresponse(u', '.join(d.getdefstr() for d in definitions))
        else:
            suggestions = connection.match(dictionary, 'lev', word.encode('utf-8'))
            if suggestions:
                event.addresponse(
                        u"I don't know about %(word)s. Maybe you meant %(suggestions)s?", {
                            'word': word,
                            'suggestions': human_join(
                                self.reduce_suggestions(suggestions),
                                conjunction=u'or'),
                })
            else:
                event.addresponse(u"I don't have a definition for that. Is it even a word?")

    @match(r'^spell\s+(.+?)(?:\s+using\s+(.+))?$')
    def handle_spell(self, event, word, strategy):
        connection = Connection(self.server, self.port)
        word = word.encode('utf-8')
        strategies = connection.getstratdescs().keys()

        if connection.match('*', 'exact', word):
            event.addresponse(choice((
                u'That seems correct. Carry on',
                u'Looks good to me',
                u"Yup, that's a word all right",
                u'Yes, you *can* spell',
            )))
            return

        strategy = strategy is None and 'lev' or strategy.lower()
        if strategy not in strategies:
            event.addresponse(
                    u"I'm afraid I don't know about such a strategy. I know about: %s",
                    human_join(sorted(strategies)))

        suggestions = connection.match('*', strategy, word)
        if suggestions:
            event.addresponse(u'Suggestions: %s', human_join(
                    self.reduce_suggestions(suggestions), conjunction=u'or'))
        else:
            event.addresponse(u"That doesn't seem correct, but I can't find anything to suggest")

    @match(r'^dictionaries$')
    def handle_dictionaries(self, event):
        connection = Connection(self.server, self.port)
        dictionaries = connection.getdbdescs()
        event.addresponse(u'My Dictionaries: %s', human_join(sorted(dictionaries.keys())))

    @match(r'^strater?gies$')
    def handle_strategies(self, event):
        connection = Connection(self.server, self.port)
        strategies = connection.getstratdescs()
        event.addresponse(u'My Strategies: %s', human_join(sorted(strategies.keys())))

    @match(r'^dictionary\s+(.+?)$')
    def handle_dictionary(self, event, dictionary):
        connection = Connection(self.server, self.port)
        dictionaries = connection.getdbdescs()
        dictionary = dictionary.lower()
        if dictionary in dictionaries:
            event.addresponse(unicode(dictionaries[dictionary]))
        else:
            event.addresponse(u"I don't have that dictionary")

    @match(r'^strater?gy\s+(.+?)$')
    def handle_strategy(self, event, strategy):
        connection = Connection(self.server, self.port)
        strategies = connection.getstratdescs()
        strategy = strategy.lower()
        if strategy in strategies:
            event.addresponse(unicode(strategies[strategy]))
        else:
            event.addresponse(u"I don't have that strategy")
Ejemplo n.º 24
0
Archivo: rfc.py Proyecto: vhata/ibid
class RFCLookup(Processor):
    usage = u"""rfc <number>
    rfc [for] <search terms>
    rfc [for] /regex/"""
    features = ('rfc', )

    indexurl = Option('index_url', "A HTTP url for the RFC Index file",
                      "http://www.rfc-editor.org/rfc/rfc-index.txt")
    cachetime = IntOption("cachetime", "Time to cache RFC index for",
                          cachetime)
    indexfile = None
    last_checked = 0

    def _update_list(self):
        if not self.indexfile or time.time(
        ) - self.last_checked > self.cachetime:
            self.indexfile = cacheable_download(self.indexurl,
                                                "rfc/rfc-index.txt")
            self.last_checked = time.time()

    class RFC(object):

        special_authors = (
            "Ed\.",
            "Eds\.",
            "RFC Editor",
            "IAP",
            "et al\.",
            "IAB",
            "IAB and IESG",
            "Internet Architecture Board",
            "Defense Advanced Research Projects Agency",
            "Internet Activities Board",
            "Gateway Algorithms and Data Structures Task Force",
            "International Organization for Standardization",
            "IAB Advisory Committee",
            "Federal Networking Council",
            "Internet Engineering Steering Group",
            "The Internet Society",
            "Sun Microsystems",
            "KOI8-U Working Group",
            "ISOC Board of Trustees",
            "Internet Assigned Numbers Authority \(IANA\)",
            "The North American Directory Forum",
            "Vietnamese Standardization Working Group",
            "ESnet Site Coordinating Comittee \(ESCC\)",
            "Energy Sciences Network \(ESnet\)",
            "North American Directory Forum",
            "Stanford Research Institute",
            "National Research Council",
            "Information Sciences Institute University of Southern California",
            "Bolt Beranek and Newman Laboratories",
            "International Telegraph and Telephone Consultative Committee of the International Telecommunication Union",
            "National Bureau of Standards",
            "Network Technical Advisory Group",
            "National Science Foundation",
            "End-to-End Services Task Force",
            "NetBIOS Working Group in the Defense Advanced Research Projects Agency",
            "ESCC X.500/X.400 Task Force",
        )
        # She's pretty, isn't she?
        # Beginners guide:
        # First line is title, initials
        # Second is middle names, surnames, and suffixes
        # Third is date and extensions
        record_re = re.compile(
            r"^(.+?)\. ((?:(?:[A-Z]{1,2}|[A-Z]\.-?[A-Z]|[A-Z]-[A-Z]|[A-Z]\([A-Z]\)|[A-Z][a-z]+)\.{0,2}"
            r"(?: (?:[Vv]an|[Dd]e[nr]?|[Ll][ae]|El|Del|Dos|da))* ?[a-zA-Z\-']+(?:[\.,]? (?:\d+(?:rd|nd|st|th)|Jr|I+)\.?)?|%s)"
            r"(?:, ?)?)+\. ([A-Z][a-z]{2,8}(?: \d{1,2})? \d{4})\. \((.+)\)$" %
            "|".join(special_authors))

        def __init__(self, number, record):
            self.number = number
            self.record = unicode(record, encoding="ASCII")

            self.issued = not self.record == "Not Issued."
            self.summary = self.record

        def parse(self):
            if self.issued:
                m = self.record_re.match(self.record)
                if not m:
                    log.warning("CAN'T DECODE RFC: " + self.record)
                else:
                    self.title, self.authors, self.date, extensions = m.groups(
                    )
                    extensions = extensions.split(') (')
                    self.formats = []
                    self.status = None
                    self.also = None
                    self.obsoleted = self.obsoletes = None
                    self.updated = self.updates = None
                    self.online = True
                    for ex in extensions:
                        if ex.startswith("Format:"):
                            self.formats = [
                                fmt.strip()
                                for fmt in ex.split(":", 1)[1].split(",")
                            ]
                        elif ex.startswith("Status:"):
                            self.status = ex.split(":", 1)[1].strip()
                        elif ex == "Not online":
                            self.online = False
                        else:
                            values = [
                                fmt.strip()
                                for fmt in ex.split(" ", 1)[1].split(",")
                            ]
                            values = [
                                val[:3] == "RFC" and val[3:] or val
                                for val in values
                            ]
                            if ex.startswith("Also"):
                                self.also = values
                            elif ex.startswith("Obsoleted by"):
                                self.obsoleted = values
                            elif ex.startswith("Obsoletes"):
                                self.obsoletes = values
                            elif ex.startswith("Updated by"):
                                self.updated = values
                            elif ex.startswith("Updates"):
                                self.updates = values
                            else:
                                log.warning("CAN'T DECODE RFC: " + self.record)

                    extensions = [
                        ":" in ex and ex.split(":", 1) or ex.split(" ", 1)
                        for ex in extensions if ":" in ex
                    ]
                    extensions = dict([(name.strip().upper(), values.strip())
                                       for name, values in extensions])
                    self.extensions = extensions
                    self.summary = u"%s. %s." % (self.title, self.date)
                    if self.status:
                        self.summary += u" " + self.status
                    if self.obsoleted:
                        self.summary += u" Obsoleted by " + u", ".join(
                            self.obsoleted)

    def _parse_rfcs(self):
        self._update_list()

        f = file(self.indexfile, "rU")
        lines = f.readlines()
        f.close()

        breaks = 0
        strip = -1
        for lineno, line in enumerate(lines):
            if line.startswith(20 * "~"):
                breaks += 1
            elif breaks == 2 and line.startswith("000"):
                strip = lineno
                break
        lines = lines[strip:]

        rfcs = {}
        buf = ""
        # So there's nothing left in buf:
        lines.append("")
        for line in lines:
            line = line.strip()
            if line:
                buf += " " + line
            elif buf:
                number, desc = buf.strip().split(None, 1)
                number = int(number)
                rfcs[number] = self.RFC(number, desc)
                buf = ""

        return rfcs

    @match(r'^rfc\s+#?(\d+)$')
    def lookup(self, event, number):
        rfcs = self._parse_rfcs()

        number = int(number)
        if number in rfcs:
            event.addresponse(
                u"%(record)s http://www.rfc-editor.org/rfc/rfc%(number)i.txt",
                {
                    'record': rfcs[number].record,
                    'number': number,
                })
        else:
            event.addresponse(u"Sorry, no such RFC")

    @match(r'^rfc\s+(?:for\s+)?(.+)$')
    def search(self, event, terms):
        # If it's an RFC number, lookup() will catch it
        if terms.isdigit():
            return

        rfcs = self._parse_rfcs()

        # Search engines:
        pool = rfcs.itervalues()
        if len(terms) > 2 and terms[0] == terms[-1] == "/":
            try:
                term_re = re.compile(terms[1:-1], re.I)
            except re.error:
                event.addresponse(u"Couldn't search. Invalid regex: %s",
                                  re.message)
                return
            pool = [rfc for rfc in pool if term_re.search(rfc.record)]

        else:
            terms = set(terms.split())
            for term in terms:
                pool = [
                    rfc for rfc in pool if term.lower() in rfc.record.lower()
                ]

        # Newer RFCs matter more:
        pool.reverse()

        if pool:
            results = []
            for result in pool[:5]:
                result.parse()
                results.append("%04i: %s" % (result.number, result.summary))
            event.addresponse(
                u'Found %(found)i matching RFCs. Listing %(listing)i: %(results)s',
                {
                    'found': len(pool),
                    'listing': min(len(pool), 5),
                    'results': u',  '.join(results),
                })
        else:
            event.addresponse(u"Sorry, can't find anything")
Ejemplo n.º 25
0
Archivo: smtp.py Proyecto: 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'])
Ejemplo n.º 26
0
Archivo: dc.py Proyecto: vhata/ibid
class SourceFactory(protocol.ReconnectingClientFactory, IbidSourceFactory):
    protocol = DCBot

    supports = ['multiline', 'topic']
    auth = ('op', )

    port = IntOption('port', 'Server port number', 411)
    server = Option('server', 'Server hostname')
    nick = Option('nick', 'DC nick', ibid.config['botname'])
    password = Option('password', 'Password', None)
    interest = Option('interest', 'User Description', '')
    speed = Option('speed', 'Bandwidth', '1kbps')
    email = Option('email', 'eMail Address', 'http://ibid.omnia.za.net/')
    sharesize = IntOption('sharesize', 'DC Share Size (bytes)', 0)
    slots = IntOption('slots', 'DC Open Slots', 0)
    action_prefix = Option('action_prefix', 'Command for actions (i.e. +me)',
                           None)
    banned_prefixes = Option('banned_prefixes',
                             'Prefixes not allowed in bot responses, i.e. !',
                             '')
    max_message_length = IntOption('max_message_length',
                                   'Maximum length of messages', 490)
    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.log = logging.getLogger('source.%s' % self.name)
        self._auth = {}

    def setup(self):
        if self.action_prefix is None and 'action' in self.supports:
            self.supports.remove('action')
        if self.action_prefix is not None and 'action' not in self.supports:
            self.supports.append('action')

    def setServiceParent(self, service):
        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 truncation_point(self, response, event=None):
        return self.max_message_length

    def _dc_auth_callback(self, nick, result):
        self._auth[nick] = result

    def auth_op(self, event, credential):
        nick = event.sender['nick']
        if nick in self.proto.hub_users and self.proto.hub_users[nick].op in (
                True, False):
            return self.proto.hub_users[nick].op

        reactor.callFromThread(self.proto.authenticate, nick,
                               self._dc_auth_callback)
        for i in xrange(150):
            if nick in self._auth:
                break
            sleep(0.1)

        if nick in self._auth:
            result = self._auth[nick]
            del self._auth[nick]
            return result

    def url(self):
        return u'dc://%s@%s:%s' % (self.nick, self.server, self.port)
Ejemplo n.º 27
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
Ejemplo n.º 28
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