Exemple #1
0
def format_event(bot, event, tzinfo, full=True):
    """Given a metabot.calendars.base.Calendar event, build a human-friendly representation."""

    message = '<b>%s</b>' % event['summary']
    for count, url in tickets.get_info(event['description']):
        if count:
            message = '%s [<a href="%s">%i tickets remaining</a>]' % (
                message, url, count)
        else:
            message = '%s [<a href="%s">tickets</a>]' % (message, url)
    message = '%s\n<a href="%s">%s</a>' % (
        message,
        bot.encode_url('/events %s %s' % (event['local_id'], tzinfo.zone)),
        humanize_range(event['start'], event['end'], tzinfo))
    if event['location']:
        location_name = event['location'].split(',', 1)[0]
        location_url = 'https://maps.google.com/maps?' + urllib.parse.urlencode({
            'q': event['location'].encode('utf-8'),
        })  # yapf: disable
        message = '%s @ <a href="%s">%s</a>' % (message, location_url,
                                                location_name)
        geo = format_geo(event['location'], event['start'])
        if geo:
            message = '%s\n\u26a0 %s' % (message, geo)
    if full and event['description']:
        message = '%s\n\n%s' % (message, html.sanitize(event['description']))
    return message
Exemple #2
0
def _daily_messages(multibot, records):  # pylint: disable=too-many-branches,too-many-locals,too-many-statements
    now = time.time()
    for botuser, botconf in multibot.conf['bots'].items():  # pylint: disable=too-many-nested-blocks
        for groupid, groupconf in botconf['issue37']['moderator'].items():
            calcodes, tzinfo, count, days, hour, dow = eventutil.get_group_conf(
                groupconf)
            if not tzinfo or not isinstance(hour, int):
                continue
            # See https://github.com/nmlorg/metabot/issues/26.
            bot = ntelebot.bot.Bot(botconf['issue37']['telegram']['token'])
            bot.multibot = multibot
            form = lambda event: eventutil.format_event(
                bot, event, tzinfo, full=False)  # pylint: disable=cell-var-from-loop
            title = lambda event: '  \u2022 ' + event['summary']
            nowdt = datetime.datetime.fromtimestamp(now, tzinfo)
            key = (botuser, groupid)
            if nowdt.hour == hour and not dow & 1 << nowdt.weekday():
                events, alerts = eventutil.get_group_events(
                    bot, calcodes, tzinfo, count, days, now)
                _handle_alerts(bot, records, groupid, alerts)
                if events:
                    preambles = groupconf['daily'].get('text', '').splitlines()
                    preamble = (preambles and
                                preambles[nowdt.toordinal() % len(preambles)]
                                or '')
                    text = _format_daily_message(preamble,
                                                 list(map(form, events)))
                    url = eventutil.get_image(events[0], botconf)
                    message = None
                    if url:
                        try:
                            message = bot.send_photo(chat_id=groupid,
                                                     photo=url,
                                                     caption=text,
                                                     parse_mode='HTML',
                                                     disable_notification=True)
                        except ntelebot.errors.Error:
                            logging.exception('Downgrading to plain text:')
                    if not message:
                        try:
                            message = bot.send_message(
                                chat_id=groupid,
                                text=text,
                                parse_mode='HTML',
                                disable_web_page_preview=True,
                                disable_notification=True)
                        except ntelebot.errors.Error:
                            logging.exception('While sending to %s:\n%s',
                                              groupid, text)
                    if message:
                        records[key] = (now,
                                        [event.copy()
                                         for event in events], message)
            elif key in records:
                lastnow, lastevents, lastmessage = records[key]
                lastmap = {event['local_id']: event for event in lastevents}
                events, alerts = eventutil.get_group_events(
                    bot, calcodes, tzinfo, count, days, lastnow)
                _handle_alerts(bot, records, groupid, alerts)
                curmap = {event['local_id']: event for event in events}
                bothevents = events.copy()
                bothevents.extend(event for event in lastevents
                                  if event['local_id'] not in curmap)
                bothevents.sort(key=operator.itemgetter(
                    'start', 'end', 'summary', 'local_id'))
                edits = []
                for event in bothevents:
                    lastevent = lastmap.get(event['local_id'])
                    if not lastevent:
                        edits.append(title(event))
                        edits.append('    \u25e6 New event!')
                        continue
                    event = multibot.multical.get_event(event['local_id'])[1]
                    if not event:
                        edits.append(title(lastevent))
                        edits.append('    \u25e6 Removed.')
                        continue
                    pairs = (
                        (lastevent['summary'], event['summary']),
                        (eventutil.humanize_range(lastevent['start'], lastevent['end'], tzinfo),
                         eventutil.humanize_range(event['start'], event['end'], tzinfo)),
                        (lastevent['location'], event['location']),
                        (html.sanitize(lastevent['description'], strip=True),
                         html.sanitize(event['description'], strip=True)),
                    )  # yapf: disable
                    pieces = []
                    for left, right in pairs:
                        diff = _quick_diff(left, right)
                        if diff:
                            pieces.append(diff)
                    if pieces:
                        edits.append(title(event))
                        for left, right in pieces:
                            edits.append(
                                '    \u25e6 <i>%s</i> \u2192 <b>%s</b>' %
                                (left, right))

                if not edits:
                    continue
                text = 'Updated:\n' + '\n'.join(edits)
                try:
                    message = bot.send_message(
                        chat_id=groupid,
                        reply_to_message_id=lastmessage['message_id'],
                        text=text,
                        parse_mode='HTML',
                        disable_web_page_preview=True,
                        disable_notification=True)
                except ntelebot.errors.Error:
                    logging.exception('While sending to %s:\n%s', groupid,
                                      text)
                    continue

                lastnowdt = datetime.datetime.fromtimestamp(lastnow, tzinfo)
                preambles = groupconf['daily'].get('text', '').splitlines()
                preamble = preambles and preambles[lastnowdt.toordinal() %
                                                   len(preambles)] or ''
                updated = 'Updated ' + humanize.time(nowdt)
                groupidnum = int(groupid)
                if -1002147483647 <= groupidnum < -1000000000000:
                    updated = '<a href="https://t.me/c/%s/%s">%s</a>' % (
                        -1000000000000 - groupidnum, message['message_id'],
                        updated)
                else:
                    updated = '%s (%s)' % (updated, message['message_id'])
                text = '%s\n\n[%s]' % (_format_daily_message(
                    preamble, list(map(form, events))), updated)
                try:
                    if lastmessage.get('caption'):
                        message = bot.edit_message_caption(
                            chat_id=groupid,
                            message_id=lastmessage['message_id'],
                            caption=text,
                            parse_mode='HTML')
                    else:
                        message = bot.edit_message_text(
                            chat_id=groupid,
                            message_id=lastmessage['message_id'],
                            text=text,
                            parse_mode='HTML',
                            disable_web_page_preview=True)
                except ntelebot.errors.Error:
                    logging.exception('While sending to %s:\n%s', groupid,
                                      text)
                else:
                    records[key] = (lastnow,
                                    [event.copy()
                                     for event in events], message)
Exemple #3
0
def test_strip():
    """Test stripping logic."""

    assert html.sanitize('', True) == ''
    assert html.sanitize('empty', True) == 'empty'
    assert html.sanitize('&#8592; &#x2190; &larr; \u2190', True) == '← ← ← ←'
    assert html.sanitize('&#38; &#x26; &amp;', True) == '&amp; &amp; &amp;'

    # From https://core.telegram.org/bots/api#formatting-options.
    text = """
<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<a href="http://www.example.com/">inline URL</a>
<a href="tg://user?id=123456789">inline mention of a user</a>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
"""
    assert html.sanitize(text, True) == """
bold, bold
italic, italic
inline URL
inline mention of a user
inline fixed-width code
pre-formatted fixed-width code block
"""
    assert html.sanitize('first<br>second', True) == 'first\nsecond'
    assert html.sanitize('<a weird="true">text</a>', True) == 'text'

    assert html.sanitize('<b>good</b> <span>bad</span>', True) == 'good bad'
    assert html.sanitize('<b><span>nested</span></b>', True) == 'nested'
    assert html.sanitize('<span><b>nested</b></span>', True) == 'nested'
    assert html.sanitize('<b>broken <i>good</i>', True) == 'broken good'

    assert html.sanitize('&bogus;', True) == '&amp;bogus;'
    assert html.sanitize('&#bogus;', True) == '&amp;#bogus;'
    assert html.sanitize('&#xxbogus;', True) == '&amp;#xxbogus;'
Exemple #4
0
def test_sanitize():
    """Test sanitization logic."""

    assert html.sanitize('') == ''
    assert html.sanitize('empty') == 'empty'
    assert html.sanitize('&#8592; &#x2190; &larr; \u2190') == '← ← ← ←'
    assert html.sanitize('&#38; &#x26; &amp;') == '&amp; &amp; &amp;'

    # From https://core.telegram.org/bots/api#formatting-options.
    text = """
<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<a href="http://www.example.com/">inline URL</a>
<a href="tg://user?id=123456789">inline mention of a user</a>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
"""
    assert html.sanitize(text) == text
    assert html.sanitize('a<br>b<div>c</div><p>d</p>') == 'a\nb\nc\nd'
    assert html.sanitize('<a weird="true">text</a>') == '<a>text</a>'

    assert html.sanitize('<b>good</b> <span>bad</span>') == '<b>good</b> bad'
    assert html.sanitize('<b><span>nested</span></b>') == '<b>nested</b>'
    assert html.sanitize('<span><b>nested</b></span>') == '<b>nested</b>'
    assert html.sanitize(
        '<b>broken <i>good</i>') == '<b>broken </b><i>good</i>'

    assert html.sanitize('&bogus;') == '&amp;bogus;'
    assert html.sanitize('&#bogus;') == '&amp;#bogus;'
    assert html.sanitize('&#xxbogus;') == '&amp;#xxbogus;'
Exemple #5
0
def inline(ctx, modconf):  # pylint: disable=too-many-branches,too-many-locals
    """Handle @BOTNAME events."""

    user_id = '%s' % ctx.user['id']
    userconf = modconf['users'][user_id]
    calcodes = userconf.get('calendars')
    timezone = userconf.get('timezone')
    if not calcodes or not timezone:
        missing = []
        if not calcodes:
            missing.append('choose one or more calendars')
        if not timezone:
            missing.append('set your time zone')
        return ctx.reply_inline([],
                                is_personal=True,
                                cache_time=30,
                                switch_pm_text='Click to %s!' % humanize.list(missing),
                                switch_pm_parameter='L2V2ZW50cw')

    calendar_view = ctx.bot.multibot.multical.view(calcodes.split())
    tzinfo = pytz.timezone(timezone)

    terms = ctx.text.lower().split()[1:]
    full = False
    if terms and terms[0].lower() == 'full':
        terms.pop(0)
        full = True
    nextid = None
    results = []
    while len(results) < 25:
        _, event, nextev = calendar_view.get_event(nextid)
        nextid = nextev and nextev['local_id']
        if not event:
            break
        if full:
            text = ('%s %s' % (event['summary'], event['description'])).lower()
        else:
            text = event['summary'].lower()
        for term in terms:
            if term not in text:
                break
        else:
            subtitle = eventutil.humanize_range(event['start'], event['end'], tzinfo)
            if event['location']:
                subtitle = '%s @ %s' % (subtitle, event['location'].split(',', 1)[0])
            if full and event['description']:
                title = '%s \u2022 %s' % (event['summary'], subtitle)
                description = html.sanitize(event['description'], strip=True)
            else:
                title = event['summary']
                description = subtitle
            results.append({
                'description': description,
                'input_message_content': {
                    'disable_web_page_preview': True,
                    'message_text': eventutil.format_event(ctx.bot, event, tzinfo, full=full),
                    'parse_mode': 'HTML',
                },
                'id': event['local_id'],
                #'thumb_url': icon,
                'title': title,
                'type': 'article',
            })
        if not nextid:
            break
    ctx.reply_inline(results,
                     is_personal=True,
                     cache_time=30,
                     switch_pm_text='Settings',
                     switch_pm_parameter='L2V2ZW50cyBzZXQ')